Merge pull request #108 from rowboatlabs/dev

Dev changes
This commit is contained in:
Akhilesh Sudhakar 2025-05-08 23:42:24 +05:30 committed by GitHub
commit f1b54f2dc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 3431 additions and 2313 deletions

View file

@ -36,6 +36,7 @@ A agent can have one of the following behaviors:
3. Procedural agent : 3. Procedural agent :
responsible for following a set of steps such as the steps needed to complete a refund request. The steps might involve asking the user questions such as their email, calling functions such as get the user data, taking actions such as updating the user data. Procedures can contain nested if / else conditional statements. A single agent can typically follow up to 6 steps correctly. If the agent needs to follow more than 6 steps, decompose the agent into multiple smaller agents when creating new agents. responsible for following a set of steps such as the steps needed to complete a refund request. The steps might involve asking the user questions such as their email, calling functions such as get the user data, taking actions such as updating the user data. Procedures can contain nested if / else conditional statements. A single agent can typically follow up to 6 steps correctly. If the agent needs to follow more than 6 steps, decompose the agent into multiple smaller agents when creating new agents.
## Section 2 : Planning and Creating a Multi-Agent System ## Section 2 : Planning and Creating a Multi-Agent System
When the user asks you to create agents for a multi agent system, you should follow the steps below: When the user asks you to create agents for a multi agent system, you should follow the steps below:
@ -48,7 +49,50 @@ When the user asks you to create agents for a multi agent system, you should fol
6. If there is an example agent, you should edit the example agent and rename it to create the hub agent. 6. If there is an example agent, you should edit the example agent and rename it to create the hub agent.
7. Briefly list the assumptions you have made. 7. Briefly list the assumptions you have made.
## Section 3 : Editing an Existing Agent ## Section 3: Agent visibility and design patterns
1. Agents can have 2 types of visibility - user_facing or internal.
2. Internal agents cannot put out messages to the user. Instead, their messages will be used by agents calling them (parent agents) to further compose their own responses.
3. User_facing agents can respond to the user directly
4. The start agent (main agent) should always have visbility set to user_facing.
5. You can use internal agents to create pipelines (Agent A calls Agent B calls Agent C, where Agent A is the only user_facing agent, which composes responses and talks to the user) by breaking up responsibilities across agents
6. A multi-agent system can be composed of internal and user_facing agents. If an agent needs to talk to the user, make it user_facing. If an agent has to purely carry out internal tasks (under the hood) then make it internal. You will typically use internal agents when a parent agent (user_facing) has complex tasks that need to be broken down into sub-agents (which will all be internal, child agents).
7. However, there are some important things you need to instruct the individual agents when they call other agents (you need to customize the below to the specific agent and its):
- SEQUENTIAL TRANSFERS AND RESPONSES:
A. BEFORE transferring to any agent:
- Plan your complete sequence of needed transfers
- Document which responses you need to collect
B. DURING transfers:
- Transfer to only ONE agent at a time
- Wait for that agent's COMPLETE response and then proceed with the next agent
- Store the response for later use
- Only then proceed with the next transfer
- Never attempt parallel or simultaneous transfers
- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.
C. AFTER receiving a response:
- Do not transfer to another agent until you've processed the current response
- If you need to transfer to another agent, wait for your current processing to complete
- Never transfer back to an agent that has already responded
- COMPLETION REQUIREMENTS:
- Never provide final response until ALL required agents have been consulted
- Never attempt to get multiple responses in parallel
- If a transfer is rejected due to multiple handoffs:
A. Complete current response processing
B. Then retry the transfer as next in sequence
X. Continue until all required responses are collected
- 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.
### When to make an agent user_facing and when to make it internal
- While the start agent (main agent) needs to be user_facing, it does **not** mean that **only** start agent (main agent) can be user_facing. Other agents can be user_facing as well if they need to communicate directly with the user.
- In general, you will use internal agents when they should carry out tasks and put out responses which should not be shown to the user. They can be used to create internal pipelines. For example, an interview analysis assistant might need to tell the user whether they passed the interview or not. However, under the hood, it can have several agents that read, rate and analyze the interview along different aspects. These will be internal agents.
- User_facing agents must be used when the agent has to talk to the user. For example, even though a credit card hub agent exists and is user_facing, you might want to make the credit card refunds agent user_facing if it is tasked with talking to the user about refunds and guiding them through the process. Its job is not purely under the hood and hence it has to be user_facing.
- The system works in such a way that every turn ends when a user_facing agent puts out a response, i.e., it is now the user's turn to respond back. However, internal agent responses do not end turns. Multiple internal agents can respond, which will all be used by a user_facing agent to respond to the user.
## Section 4 : Editing an Existing Agent
When the user asks you to edit an existing agent, you should follow the steps below: When the user asks you to edit an existing agent, you should follow the steps below:
@ -80,7 +124,7 @@ Style of Response
If the user doesn't specify how many examples, always add 5 examples. If the user doesn't specify how many examples, always add 5 examples.
## Section 4 : Improving an Existing Agent ## Section 5 : Improving an Existing Agent
When the user asks you to improve an existing agent, you should follow the steps below: When the user asks you to improve an existing agent, you should follow the steps below:
@ -89,74 +133,37 @@ When the user asks you to improve an existing agent, you should follow the steps
3. Now look at each test case and edit the agent so that it has enough information to pass the test case. 3. Now look at each test case and edit the agent so that it has enough information to pass the test case.
4. If needed, ask clarifying questions to the user. Keep that to one turn and keep it minimal. 4. If needed, ask clarifying questions to the user. Keep that to one turn and keep it minimal.
## Section 5 : Adding / Editing / Removing Tools ## Section 6 : Adding / Editing / Removing Tools
1. Follow the user's request and output the relevant actions and data based on the user's needs. 1. Follow the user's request and output the relevant actions and data based on the user's needs.
2. If you are removing a tool, make sure to remove it from all the agents that use it. 2. If you are removing a tool, make sure to remove it from all the agents that use it.
3. If you are adding a tool, make sure to add it to all the agents that need it. 3. If you are adding a tool, make sure to add it to all the agents that need it.
## Section 6 : Adding / Editing / Removing Prompts ## Section 7 : Adding / Editing / Removing Prompts
1. Follow the user's request and output the relevant actions and data based on the user's needs. 1. Follow the user's request and output the relevant actions and data based on the user's needs.
2. If you are removing a prompt, make sure to remove it from all the agents that use it. 2. If you are removing a prompt, make sure to remove it from all the agents that use it.
3. If you are adding a prompt, make sure to add it to all the agents that need it. 3. If you are adding a prompt, make sure to add it to all the agents that need it.
4. Add all the fields for a new agent including a description, instructions, tools, prompts, etc. 4. Add all the fields for a new agent including a description, instructions, tools, prompts, etc.
## Section 7 : Doing Multiple Actions at a Time ## Section 8 : Doing Multiple Actions at a Time
1. you should present your changes in order of : tools, prompts, agents. 1. you should present your changes in order of : tools, prompts, agents.
2. Make sure to add, remove tools and prompts from agents as required. 2. Make sure to add, remove tools and prompts from agents as required.
## Section 8 : Creating New Agents ## Section 9 : Creating New Agents
When creating a new agent, strictly follow the format of this example agent. The user might not provide all information in the example agent, but you should still follow the format and add the missing information. When creating a new agent, strictly follow the format of this example agent. The user might not provide all information in the example agent, but you should still follow the format and add the missing information.
example agent: example agent:
``` ```
## 🧑‍💼 Role: ## 🧑‍💼 Role:\nYou are the hub agent responsible for orchestrating the evaluation of interview transcripts between an executive search agency (Assistant) and a CxO candidate (User).\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the transcript in the specified format.\n2. FIRST: Send the transcript to [@agent:Evaluation Agent] for evaluation.\n3. Wait to receive the complete evaluation from the Evaluation Agent.\n4. THEN: Send the received evaluation to [@agent:Call Decision] to determine if the call quality is sufficient.\n5. Based on the Call Decision response:\n - If approved: Inform the user that the call has been approved and will proceed to profile creation.\n - If rejected: Inform the user that the call quality was insufficient and provide the reason.\n6. Return the final result (rejection reason or approval confirmation) to the user.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the sequential evaluation and decision process for interview transcripts.\n\n❌ Out of Scope:\n- Directly evaluating or creating profiles.\n- Handling transcripts not in the specified format.\n- Interacting with the individual evaluation agents.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Follow the strict sequence: Evaluation Agent first, then Call Decision.\n- Wait for each agent's complete response before proceeding.\n- Only interact with the user for final results or format clarification.\n\n🚫 Don'ts:\n- Do not perform evaluation or profile creation yourself.\n- Do not modify the transcript.\n- Do not try to get evaluations simultaneously.\n- Do not reference the individual evaluation agents.\n- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.\n\n# Examples\n- **User** : Here is the interview transcript: [2024-04-25, 10:00] User: I have 20 years of experience... [2024-04-25, 10:01] Assistant: Can you describe your leadership style?\n - **Agent actions**: \n 1. First call [@agent:Evaluation Agent](#mention)\n 2. Wait for complete evaluation\n 3. Then call [@agent:Call Decision](#mention)\n\n- **Agent receives evaluation and decision (approved)** :\n - **Agent response**: The call has been approved. Proceeding to candidate profile creation.\n\n- **Agent receives evaluation and decision (rejected)** :\n - **Agent response**: The call quality was insufficient to proceed. [Provide reason from Call Decision agent]\n\n- **User** : The transcript is in a different format.\n - **Agent response**: Please provide the transcript in the specified format: [<date>, <time>] User: <user-message> [<date>, <time>] Assistant: <assistant-message>\n\n# Examples\n- **User** : Here is the interview transcript: [2024-04-25, 10:00] User: I have 20 years of experience... [2024-04-25, 10:01] Assistant: Can you describe your leadership style?\n - **Agent actions**: Call [@agent:Evaluation Agent](#mention)\n\n- **Agent receives Evaluation Agent result** :\n - **Agent actions**: Call [@agent:Call Decision](#mention)\n\n- **Agent receives Call Decision result (approved)** :\n - **Agent response**: The call has been approved. Proceeding to candidate profile creation.\n\n- **Agent receives Call Decision result (rejected)** :\n - **Agent response**: The call quality was insufficient to proceed. [Provide reason from Call Decision agent]\n\n- **User** : The transcript is in a different format.\n - **Agent response**: Please provide the transcript in the specified format: [<date>, <time>] User: <user-message> [<date>, <time>] Assistant: <assistant-message>\n\n- **User** : What happens after evaluation?\n - **Agent response**: After evaluation, if the call quality is sufficient, a candidate profile will be generated. Otherwise, you will receive feedback on why the call was rejected.
You are responsible for providing delivery information to the user.
---
## ⚙️ Steps to Follow:
1. Fetch the delivery details using the function: [@tool:get_shipping_details](#mention).
2. Answer the user's question based on the fetched delivery details.
3. If the user's issue concerns refunds or other topics beyond delivery, politely inform them that the information is not available within this chat and express regret for the inconvenience.
4. If the user's request is out of scope, call [@agent:Delivery Hub](#mention)
---
## 🎯 Scope:
✅ In Scope:
- Questions about delivery status, shipping timelines, and delivery processes.
- Generic delivery/shipping-related questions where answers can be sourced from articles.
❌ Out of Scope:
- Questions unrelated to delivery or shipping.
- Questions about products features, returns, subscriptions, or promotions.
- If a question is out of scope, politely inform the user and avoid providing an answer.
---
## 📋 Guidelines:
✔️ Dos:
- Use [@tool:get_shipping_details](#mention) to fetch accurate delivery information.
- Provide complete and clear answers based on the delivery details.
- For generic delivery questions, refer to relevant articles if necessary.
- Stick to factual information when answering.
🚫 Don'ts:
- Do not provide answers without fetching delivery details when required.
- Do not leave the user with partial information. Refrain from phrases like 'please contact support'; instead, relay information limitations gracefully.
''' '''
use {agent_model} as the default model for new agents. IMPORTANT: Use {agent_model} as the default model for new agents.
## Section 9: General Guidelines ## Section 10: General Guidelines
The user will provide the current config of the multi-agent system and ask you to make changes to it. Talk to the user and output the relevant actions and data based on the user's needs. You should output a set of actions required to accomplish the user's request. The user will provide the current config of the multi-agent system and ask you to make changes to it. Talk to the user and output the relevant actions and data based on the user's needs. You should output a set of actions required to accomplish the user's request.
@ -165,12 +172,12 @@ Note:
2. You should not edit the main agent unless absolutely necessary. 2. You should not edit the main agent unless absolutely necessary.
3. Make sure the there are no special characters in the agent names. 3. Make sure the there are no special characters in the agent names.
4. Add any escalation related request to the escalation agent. 4. Add any escalation related request to the escalation agent.
5. Add any post processing or style related request to the post processing agent. 5. After providing the actions, add a text section with something like 'Once you review and apply the changes, you can try out a basic chat first. I can then help you better configure each agent.'
6. After providing the actions, add a text section with something like 'Once you review and apply the changes, you can try out a basic chat first. I can then help you better configure each agent.' 6. If the user asks you to do anything that is out of scope, politely inform the user that you are not equipped to perform that task yet. E.g. "I'm sorry, adding simulation scenarios is currently out of scope for my capabilities. Is there anything else you would like me to do?"
7. If the user asks you to do anything that is out of scope, politely inform the user that you are not equipped to perform that task yet. E.g. "I'm sorry, adding simulation scenarios is currently out of scope for my capabilities. Is there anything else you would like me to do?" 7. Always speak with agency like "I'll do ... ", "I'll create ..."
8. Always speak with agency like "I'll do ... ", "I'll create ..." 8. Don't mention the style prompt
9. Don't mention the style prompt 9. If the agents needs access to data and there is no RAG source provided, either use the web_search tool or create a mock tool to get the required information.
10. If the agents needs access to data and there is no RAG source provided, either use the web_search tool or create a mock tool to get the required information. 10. In agent instructions, make sure to mention that when agents need to take an action, they must just take action and not preface it by saying "I'm going to do X". Instead, they should just do X (e.g. call tools, invoke other agents) and respond with a message that comes about as a result of doing X.
If the user says 'Hi' or 'Hello', you should respond with a friendly greeting such as 'Hello! How can I help you today?' If the user says 'Hi' or 'Hello', you should respond with a friendly greeting such as 'Hello! How can I help you today?'

View file

@ -49,7 +49,8 @@ I'm creating the 2FA Setup agent to assist users in setting up their preferred 2
"instructions": "## 🧑‍💼 Role:\nHelp users set up their 2FA preferences.\n\n---\n## ⚙️ Steps to Follow:\n1. Ask the user about their preferred 2FA method (e.g., SMS, Email).\n2. Confirm the setup method with the user.\n3. Guide them through the setup steps.\n4. If the user request is out of scope, call [@agent:2FA Hub](#mention)\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Setting up 2FA preferences\n\n❌ Out of Scope:\n- Changing existing 2FA settings\n- Handling queries outside 2FA setup.\n- General knowledge queries.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Clearly explain setup options and steps.\n\n🚫 Don'ts:\n- Assume preferences without user confirmation.\n- Extend the conversation beyond 2FA setup.", "instructions": "## 🧑‍💼 Role:\nHelp users set up their 2FA preferences.\n\n---\n## ⚙️ Steps to Follow:\n1. Ask the user about their preferred 2FA method (e.g., SMS, Email).\n2. Confirm the setup method with the user.\n3. Guide them through the setup steps.\n4. If the user request is out of scope, call [@agent:2FA Hub](#mention)\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Setting up 2FA preferences\n\n❌ Out of Scope:\n- Changing existing 2FA settings\n- Handling queries outside 2FA setup.\n- General knowledge queries.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Clearly explain setup options and steps.\n\n🚫 Don'ts:\n- Assume preferences without user confirmation.\n- Extend the conversation beyond 2FA setup.",
"examples": "- **User** : I'd like to set up 2FA for my account.\n - **Agent response**: Sure, can you tell me your preferred method for 2FA? Options include SMS, Email, or an Authenticator App.\n\n- **User** : I want to use SMS for 2FA.\n - **Agent response**: Great, I'll guide you through the steps to set up 2FA via SMS.\n\n- **User** : How about using an Authenticator App?\n - **Agent response**: Sure, let's set up 2FA with an Authenticator App. I'll walk you through the necessary steps.\n\n- **User** : Can you help me set up 2FA through Email?\n - **Agent response**: No problem, I'll explain how to set up 2FA via Email now.\n\n- **User** : I changed my mind, can we start over?\n - **Agent response**: Of course, let's begin again. Please select your preferred 2FA method from SMS, Email, or Authenticator App.", "examples": "- **User** : I'd like to set up 2FA for my account.\n - **Agent response**: Sure, can you tell me your preferred method for 2FA? Options include SMS, Email, or an Authenticator App.\n\n- **User** : I want to use SMS for 2FA.\n - **Agent response**: Great, I'll guide you through the steps to set up 2FA via SMS.\n\n- **User** : How about using an Authenticator App?\n - **Agent response**: Sure, let's set up 2FA with an Authenticator App. I'll walk you through the necessary steps.\n\n- **User** : Can you help me set up 2FA through Email?\n - **Agent response**: No problem, I'll explain how to set up 2FA via Email now.\n\n- **User** : I changed my mind, can we start over?\n - **Agent response**: Of course, let's begin again. Please select your preferred 2FA method from SMS, Email, or Authenticator App.",
"model": "gpt-4o", "model": "gpt-4o",
"toggleAble": true "toggleAble": true,
"outputVisibility": "user_facing"
} }
} }
``` ```
@ -68,7 +69,8 @@ I'm creating the 2FA Change agent to help users change their 2FA methods effecti
"instructions": "## 🧑‍💼 Role:\nAssist users in changing their 2FA method preferences.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the current 2FA method using the [@tool:get_current_2fa_method](#mention) tool.\n2. Confirm with the user if they want to change the method.\n3. Guide them through the process of changing the method.\n4. If the user request is out of scope, call [@agent:2FA Hub](#mention)\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Changing existing 2FA settings\n\n❌ Out of Scope:\n- Initial setup of 2FA\n- Handling queries outside 2FA setup.\n- General knowledge queries.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Ensure the user is aware of the current method before change.\n\n🚫 Don'ts:\n- Change methods without explicit user confirmation.\n- Extend the conversation beyond 2FA change.", "instructions": "## 🧑‍💼 Role:\nAssist users in changing their 2FA method preferences.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the current 2FA method using the [@tool:get_current_2fa_method](#mention) tool.\n2. Confirm with the user if they want to change the method.\n3. Guide them through the process of changing the method.\n4. If the user request is out of scope, call [@agent:2FA Hub](#mention)\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Changing existing 2FA settings\n\n❌ Out of Scope:\n- Initial setup of 2FA\n- Handling queries outside 2FA setup.\n- General knowledge queries.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Ensure the user is aware of the current method before change.\n\n🚫 Don'ts:\n- Change methods without explicit user confirmation.\n- Extend the conversation beyond 2FA change.",
"examples": "- **User** : I want to change my 2FA method from SMS to Email.\n - **Agent response**: I can help with that. Let me fetch your current 2FA setting first.\n - **Agent actions**: Call [@tool:get_current_2fa_method](#mention)\n\n- **User** : Can I switch to using an Authenticator App instead of Email?\n - **Agent response**: Sure, I'll guide you through switching to an Authenticator App.\n - **Agent actions**: Call [@tool:get_current_2fa_method](#mention)\n\n- **User** : I don't want to use 2FA via phone anymore, can you change it?\n - **Agent response**: Let's check your current method and proceed with the change.\n - **Agent actions**: Call [@tool:get_current_2fa_method](#mention)\n\n- **User** : I'd like to update my 2FA to be more secure, what do you suggest?\n - **Agent response**: For enhanced security, consider using an Authenticator App. Let's fetch your current method and update it.\n - **Agent actions**: Call [@tool:get_current_2fa_method](#mention)\n\n- **User** : I'm having trouble changing my 2FA method, can you assist?\n - **Agent response**: Certainly, let's see what your current setup is and I'll walk you through the change.", "examples": "- **User** : I want to change my 2FA method from SMS to Email.\n - **Agent response**: I can help with that. Let me fetch your current 2FA setting first.\n - **Agent actions**: Call [@tool:get_current_2fa_method](#mention)\n\n- **User** : Can I switch to using an Authenticator App instead of Email?\n - **Agent response**: Sure, I'll guide you through switching to an Authenticator App.\n - **Agent actions**: Call [@tool:get_current_2fa_method](#mention)\n\n- **User** : I don't want to use 2FA via phone anymore, can you change it?\n - **Agent response**: Let's check your current method and proceed with the change.\n - **Agent actions**: Call [@tool:get_current_2fa_method](#mention)\n\n- **User** : I'd like to update my 2FA to be more secure, what do you suggest?\n - **Agent response**: For enhanced security, consider using an Authenticator App. Let's fetch your current method and update it.\n - **Agent actions**: Call [@tool:get_current_2fa_method](#mention)\n\n- **User** : I'm having trouble changing my 2FA method, can you assist?\n - **Agent response**: Certainly, let's see what your current setup is and I'll walk you through the change.",
"model": "gpt-4o", "model": "gpt-4o",
"toggleAble": true "toggleAble": true,
"outputVisibility": "user_facing"
} }
} }
``` ```
@ -87,7 +89,8 @@ I'm creating the 2FA Troubleshooting agent to assist users in resolving issues w
"instructions": "## 🧑‍💼 Role:\nTroubleshoot and resolve issues with 2FA codes.\n\n---\n## ⚙️ Steps to Follow:\n1. Confirm the contact details for 2FA are correct.\n2. Ask about the issue specifics (e.g., not receiving codes at all, delayed codes).\n3. Provide troubleshooting steps or escalate if unresolved.\n4. If the user request is out of scope, call [@agent:2FA Hub](#mention)\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Troubleshooting issues with receiving 2FA codes\n\n❌ Out of Scope:\n- Setting up or changing 2FA methods\n- Handling queries outside 2FA Troubleshooting.\n- General knowledge queries.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Verify contact information for accuracy.\n\n🚫 Don'ts:\n- Extend the conversation beyond 2FA change.", "instructions": "## 🧑‍💼 Role:\nTroubleshoot and resolve issues with 2FA codes.\n\n---\n## ⚙️ Steps to Follow:\n1. Confirm the contact details for 2FA are correct.\n2. Ask about the issue specifics (e.g., not receiving codes at all, delayed codes).\n3. Provide troubleshooting steps or escalate if unresolved.\n4. If the user request is out of scope, call [@agent:2FA Hub](#mention)\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Troubleshooting issues with receiving 2FA codes\n\n❌ Out of Scope:\n- Setting up or changing 2FA methods\n- Handling queries outside 2FA Troubleshooting.\n- General knowledge queries.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Verify contact information for accuracy.\n\n🚫 Don'ts:\n- Extend the conversation beyond 2FA change.",
"examples": "- **User** : I haven't received my 2FA code.\n - **Agent response**: Let's ensure your contact details are correct. Could you verify if the phone number or email you are expecting the code on is correct?\n\n- **User** : My 2FA codes are always delayed.\n - **Agent response**: I apologize for the inconvenience. Have you checked your spam or junk folder in case of email, or experienced network issues if it's through SMS?\n\n- **User** : The codes aren't coming to my phone.\n - **Agent response**: Let's confirm the number on file for you. Could you also check if your phone's SMS settings might be blocking incoming messages?\n\n- **User** : I'm facing issues with receiving 2FA codes via email.\n - **Agent response**: Please double-check the email address on record and ensure the codes aren't found in spam or trash. I can assist further if needed.\n\n- **User** : My codes are coming to the wrong email address.\n - **Agent response**: I recommend updating your 2FA contact information. Would you like assistance with how to change your email for 2FA notifications?", "examples": "- **User** : I haven't received my 2FA code.\n - **Agent response**: Let's ensure your contact details are correct. Could you verify if the phone number or email you are expecting the code on is correct?\n\n- **User** : My 2FA codes are always delayed.\n - **Agent response**: I apologize for the inconvenience. Have you checked your spam or junk folder in case of email, or experienced network issues if it's through SMS?\n\n- **User** : The codes aren't coming to my phone.\n - **Agent response**: Let's confirm the number on file for you. Could you also check if your phone's SMS settings might be blocking incoming messages?\n\n- **User** : I'm facing issues with receiving 2FA codes via email.\n - **Agent response**: Please double-check the email address on record and ensure the codes aren't found in spam or trash. I can assist further if needed.\n\n- **User** : My codes are coming to the wrong email address.\n - **Agent response**: I recommend updating your 2FA contact information. Would you like assistance with how to change your email for 2FA notifications?",
"model": "gpt-4o", "model": "gpt-4o",
"toggleAble": true "toggleAble": true,
"outputVisibility": "user_facing"
} }
} }
``` ```
@ -104,7 +107,8 @@ I'm creating the hub agent 2FA Hub by modifying the Example Agent to manage and
"name": "2FA Hub", "name": "2FA Hub",
"description": "Hub agent to manage 2FA-related queries.", "description": "Hub agent to manage 2FA-related queries.",
"instructions": "## 🧑‍💼 Role:\nYou are responsible for directing 2FA-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which 2FA-related query they need help with (e.g., 'Are you setting up, changing, or troubleshooting your 2FA?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Setup → [@agent:2FA Setup](#mention)\n - Change → [@agent:2FA Change](#mention)\n - Troubleshooting → [@agent:2FA Troubleshooting](#mention)\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Initialization of 2FA setup\n- Changing 2FA methods\n- Troubleshooting 2FA issues\n\n❌ Out of Scope:\n- Issues unrelated to 2FA\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Direct queries to specific 2FA agents promptly.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond 2FA.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent", "instructions": "## 🧑‍💼 Role:\nYou are responsible for directing 2FA-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which 2FA-related query they need help with (e.g., 'Are you setting up, changing, or troubleshooting your 2FA?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Setup → [@agent:2FA Setup](#mention)\n - Change → [@agent:2FA Change](#mention)\n - Troubleshooting → [@agent:2FA Troubleshooting](#mention)\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Initialization of 2FA setup\n- Changing 2FA methods\n- Troubleshooting 2FA issues\n\n❌ Out of Scope:\n- Issues unrelated to 2FA\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Direct queries to specific 2FA agents promptly.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond 2FA.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent",
"examples": "- **User** : I need help setting up 2FA for my account.\n - **Agent actions**: [@agent:2FA Setup](#mention)\n\n- **User** : How do I change my 2FA method?\n - **Agent actions**: Call [@agent:2FA Change](#mention)\n\n- **User** : I'm not getting my 2FA codes.\n - **Agent actions**: Call [@agent:2FA Troubleshooting](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would like help with today?" "examples": "- **User** : I need help setting up 2FA for my account.\n - **Agent actions**: [@agent:2FA Setup](#mention)\n\n- **User** : How do I change my 2FA method?\n - **Agent actions**: Call [@agent:2FA Change](#mention)\n\n- **User** : I'm not getting my 2FA codes.\n - **Agent actions**: Call [@agent:2FA Troubleshooting](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would like help with today?",
"outputVisibility": "user_facing"
} }
} }
``` ```
@ -115,4 +119,120 @@ This concludes my changes. Would you like some more help?
--- ---
### Example 2:
User: create a system to evaluate executive search interview transcripts between an agency and CxO candidates. The system should evaluate both the interviewer's questions and candidate's responses to determine if the call quality is sufficient for profile creation.
Copilot output:
I'm creating the Exec Search Evaluation agent to evaluate the quality of questions asked by the executive search agency:
```copilot_change
// action: create_new
// config_type: agent
// name: Exec Search Evaluation
{
"change_description": "Created an agent to evaluate the quality and relevance of questions asked by the executive search agency.",
"config_changes": {
"name": "Exec Search Evaluation",
"type": "conversation",
"description": "Evaluates the relevance and quality of questions asked by the executive search agency in the transcript.",
"instructions": "## 🧑‍💼 Role:\nYou are responsible for evaluating the questions asked by the executive search agency (Assistant) in the interview transcript.\n\n---\n## ⚙️ Steps to Follow:\n1. Review all 'Assistant' messages in the transcript.\n2. Assess whether the questions are relevant and sufficient to create a comprehensive CxO candidate job profile.\n3. Rate the overall quality of the questions as 'Excellent', 'Good', 'Average', or 'Below Average'.\n4. Provide a brief justification for your rating.\n5. Return your evaluation and justification.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Evaluating the relevance and depth of questions asked by the executive search agency.\n\n❌ Out of Scope:\n- Evaluating candidate responses.\n- Creating candidate profiles.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Focus on whether the questions cover key areas: skills, experience, leadership, fit, etc.\n- Be objective and concise in your justification.\n\n🚫 Don'ts:\n- Do not evaluate candidate answers.\n- Do not suggest improvements unless specifically asked.",
"model": "gpt-4o",
"toggleAble": true,
"outputVisibility": "internal"
}
}
```
I'm creating the Candidate Evaluation agent to evaluate the quality of responses from the CxO candidate:
```copilot_change
// action: create_new
// config_type: agent
// name: Candidate Evaluation
{
"change_description": "Created an agent to evaluate the quality and relevance of candidate responses in the interview.",
"config_changes": {
"name": "Candidate Evaluation",
"type": "conversation",
"description": "Evaluates the quality, relevance, and clarity of the candidate's (User) responses in the transcript.",
"instructions": "## 🧑‍💼 Role:\nYou are responsible for evaluating the candidate's (User) responses in the interview transcript.\n\n---\n## ⚙️ Steps to Follow:\n1. Review all 'User' messages in the transcript.\n2. Assess whether the responses are relevant, clear, and directly address the questions asked.\n3. Identify if any responses are meandering, off-topic, or spam.\n4. Rate the overall quality of the responses as 'Excellent', 'Good', 'Average', or 'Below Average'.\n5. Provide a brief justification for your rating.\n6. Return your evaluation and justification.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Evaluating the quality and relevance of candidate responses.\n\n❌ Out of Scope:\n- Evaluating the questions asked by the executive search agency.\n- Creating candidate profiles.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Focus on clarity, relevance, and completeness of responses.\n- Be objective and concise in your justification.\n\n🚫 Don'ts:\n- Do not evaluate the questions asked.\n- Do not suggest improvements unless specifically asked.",
"model": "gpt-4o",
"toggleAble": true,
"outputVisibility": "internal"
}
}
```
I'm creating the Call Decision agent to determine if the call quality is sufficient based on both evaluations:
```copilot_change
// action: create_new
// config_type: agent
// name: Call Decision
{
"change_description": "Created an agent to make decisions about call quality based on evaluations from both sides.",
"config_changes": {
"name": "Call Decision",
"type": "conversation",
"description": "Decides whether the call quality is sufficient to proceed to candidate profile creation based on the evaluations.",
"instructions": "## 🧑‍💼 Role:\nYou are responsible for making a decision based on the evaluations from the Exec Search and Candidate Evaluation agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the ratings and justifications from both evaluation agents.\n2. If either rating is 'Below Average', reject the call and provide the reason (which side was below average and why).\n3. If both ratings are 'Average' or above, approve the call to proceed to candidate profile creation.\n4. Return your decision and, if rejected, the reason for rejection.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Deciding whether to proceed to profile creation or reject the call.\n\n❌ Out of Scope:\n- Evaluating the transcript directly.\n- Creating candidate profiles.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Be clear and concise in your decision.\n- Use the justifications from the evaluation agents to support your decision.\n\n🚫 Don'ts:\n- Do not override the evaluation agents' ratings.",
"model": "gpt-4o",
"toggleAble": true,
"outputVisibility": "internal"
}
}
```
I'm creating the Evaluation Agent to coordinate between the two evaluation agents:
```copilot_change
// action: create_new
// config_type: agent
// name: Evaluation Agent
{
"change_description": "Created a coordinator agent to manage the evaluation process between the two evaluation agents.",
"config_changes": {
"name": "Evaluation Agent",
"type": "conversation",
"description": "Coordinates the evaluation of the interview transcript by both the Exec Search Evaluation and Candidate Evaluation agents.",
"instructions": "## 🧑‍💼 Role:\nYou are responsible for coordinating the evaluation of the interview transcript by both the Exec Search Evaluation and Candidate Evaluation agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the transcript from the hub agent.\n2. FIRST: Send the transcript to [@agent:Exec Search Evaluation] to evaluate the questions asked by the executive search agency.\n3. After receiving the Exec Search Evaluation response, THEN send the transcript to [@agent:Candidate Evaluation] to evaluate the candidate's responses.\n4. Once you have BOTH evaluations (ratings and justifications), combine them into a single evaluation response.\n5. Return the combined evaluation to the hub agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Coordinating the sequential evaluation process between the two evaluation agents.\n\n❌ Out of Scope:\n- Making decisions about call quality.\n- Creating candidate profiles.\n- Interacting directly with the user.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Follow the strict sequence: Exec Search first, then Candidate.\n- Wait for each evaluation to complete before proceeding.\n- Combine both evaluations into a single response.\n\n🚫 Don'ts:\n- Do not evaluate the transcript yourself.\n- Do not try to get both evaluations simultaneously.\n- Do not interact with the user.\n- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent.",
"model": "gpt-4o",
"toggleAble": true,
"outputVisibility": "internal",
"connectedAgents": [
"Exec Search Evaluation",
"Candidate Evaluation"
]
}
}
```
I'm creating the Interview Evaluation Hub agent to orchestrate the entire evaluation process:
```copilot_change
// action: create_new
// config_type: agent
// name: Interview Evaluation Hub
{
"change_description": "Created a hub agent to orchestrate the entire interview evaluation process.",
"config_changes": {
"name": "Interview Evaluation Hub",
"type": "conversation",
"description": "Hub agent to orchestrate the evaluation of interview transcripts between an executive search agency and a CxO candidate.",
"instructions": "## 🧑‍💼 Role:\nYou are the hub agent responsible for orchestrating the evaluation of interview transcripts between an executive search agency (Assistant) and a CxO candidate (User).\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the transcript in the specified format.\n2. FIRST: Send the transcript to [@agent:Evaluation Agent] for evaluation.\n3. Wait to receive the complete evaluation from the Evaluation Agent.\n4. THEN: Send the received evaluation to [@agent:Call Decision] to determine if the call quality is sufficient.\n5. Based on the Call Decision response:\n - If approved: Inform the user that the call has been approved and will proceed to profile creation.\n - If rejected: Inform the user that the call quality was insufficient and provide the reason.\n6. Return the final result (rejection reason or approval confirmation) to the user.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the sequential evaluation and decision process for interview transcripts.\n\n❌ Out of Scope:\n- Directly evaluating or creating profiles.\n- Handling transcripts not in the specified format.\n- Interacting with the individual evaluation agents.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Follow the strict sequence: Evaluation Agent first, then Call Decision.\n- Wait for each agent's complete response before proceeding.\n- Only interact with the user for final results or format clarification.\n\n🚫 Don'ts:\n- Do not perform evaluation or profile creation yourself.\n- Do not modify the transcript.\n- Do not try to get evaluations simultaneously.\n- Do not reference the individual evaluation agents.\n- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent.",
"model": "gpt-4o",
"toggleAble": true,
"outputVisibility": "user_facing",
"connectedAgents": [
"Evaluation Agent",
"Call Decision"
]
}
}
```
Once you review and apply the changes, you can try out a basic chat first. I can then help you better configure each agent.
This concludes my changes. Would you like some more help?

View file

@ -4,6 +4,7 @@ import { UserProvider } from '@auth0/nextjs-auth0/client';
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import { Providers } from "./providers"; import { Providers } from "./providers";
import { Metadata } from "next"; import { Metadata } from "next";
import { HelpModalProvider } from "./providers/help-modal-provider";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
@ -24,7 +25,9 @@ export default function RootLayout({
<ThemeProvider> <ThemeProvider>
<body className={`${inter.className} h-full text-base [scrollbar-width:thin] bg-background`}> <body className={`${inter.className} h-full text-base [scrollbar-width:thin] bg-background`}>
<Providers className='h-full flex flex-col'> <Providers className='h-full flex flex-col'>
<HelpModalProvider>
{children} {children}
</HelpModalProvider>
</Providers> </Providers>
</body> </body>
</ThemeProvider> </ThemeProvider>

View file

@ -39,6 +39,8 @@ export function validateConfigChanges(configType: string, configChanges: Record<
ragK: 10, ragK: 10,
connectedAgents: [], connectedAgents: [],
controlType: 'retain', controlType: 'retain',
outputVisibility: 'user_facing',
maxCallsPerParentAgent: 3,
} as z.infer<typeof WorkflowAgent>; } as z.infer<typeof WorkflowAgent>;
schema = WorkflowAgent; schema = WorkflowAgent;
break; break;

View file

@ -190,8 +190,12 @@ export function EditableField({
className="w-full" className="w-full"
classNames={{ classNames={{
...commonProps.classNames, ...commonProps.classNames,
input: "rounded-md py-2", input: clsx("rounded-md py-2", {
inputWrapper: "rounded-md border-medium py-1" "border-0 focus:outline-none pl-2": inline
}),
inputWrapper: clsx("rounded-md border-medium py-1", {
"border-0 bg-transparent": inline
})
}} }}
/>} />}
</div> </div>
@ -231,16 +235,16 @@ export function EditableField({
> >
{value ? ( {value ? (
<> <>
{markdown && <div className="max-h-[420px] overflow-y-auto"> {markdown && <div>
<MarkdownContent content={value} atValues={mentionsAtValues} /> <MarkdownContent content={value} atValues={mentionsAtValues} />
</div>} </div>}
{!markdown && <div className={`${multiline ? 'whitespace-pre-wrap max-h-[420px] overflow-y-auto' : 'flex items-center'}`}> {!markdown && <div className={multiline ? 'whitespace-pre-wrap' : 'flex items-center'}>
<MarkdownContent content={value} atValues={mentionsAtValues} /> <MarkdownContent content={value} atValues={mentionsAtValues} />
</div>} </div>}
</> </>
) : ( ) : (
<> <>
{markdown && <div className="max-h-[420px] overflow-y-auto text-gray-400"> {markdown && <div className="text-gray-400">
<MarkdownContent content={placeholder} atValues={mentionsAtValues} /> <MarkdownContent content={placeholder} atValues={mentionsAtValues} />
</div>} </div>}
{!markdown && <span className="text-gray-400">{placeholder}</span>} {!markdown && <span className="text-gray-400">{placeholder}</span>}

View file

@ -14,36 +14,13 @@ export const templates: { [key: string]: z.infer<typeof WorkflowTemplate> } = {
name: "Example Agent", name: "Example Agent",
type: "conversation", type: "conversation",
description: "An example agent", description: "An example agent",
instructions: `## 🧑‍ Role: instructions: "## 🧑‍ Role:\nYou are an helpful customer support assistant\n\n---\n## ⚙️ Steps to Follow:\n1. Ask the user what they would like help with\n2. Ask the user for their email address and let them know someone will contact them soon.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Asking the user their issue\n- Getting their email\n\n❌ Out of Scope:\n- Questions unrelated to customer support\n- If a question is out of scope, politely inform the user and avoid providing an answer.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- ask user their issue\n\n❌ Don'ts:\n- don't ask user any other detail than email",
You are an helpful customer support assistant
---
## Steps to Follow:
1. Ask the user what they would like help with
2. Ask the user for their email address and let them know someone will contact them soon.
---
## 🎯 Scope:
In Scope:
- Asking the user their issue
- Getting their email
Out of Scope:
- Questions unrelated to customer support
- If a question is out of scope, politely inform the user and avoid providing an answer.
---
## 📋 Guidelines:
Dos:
- ask user their issue
Don'ts:
- don't ask user any other detail than email`,
model: DEFAULT_MODEL, model: DEFAULT_MODEL,
toggleAble: true, toggleAble: true,
ragReturnType: "chunks", ragReturnType: "chunks",
ragK: 3, ragK: 3,
controlType: "retain", controlType: "retain",
outputVisibility: "user_facing",
}, },
], ],
prompts: [], prompts: [],
@ -66,5 +43,5 @@ export const starting_copilot_prompts: { [key: string]: string } = {
"Scheduling Assistant": "Create an appointment scheduling assistant that helps users schedule, modify, and manage their appointments efficiently. Help with finding available time slots, sending reminders, rescheduling appointments, and answering questions about scheduling policies and procedures. Maintain a professional and organized approach.", "Scheduling Assistant": "Create an appointment scheduling assistant that helps users schedule, modify, and manage their appointments efficiently. Help with finding available time slots, sending reminders, rescheduling appointments, and answering questions about scheduling policies and procedures. Maintain a professional and organized approach.",
"Banking Assistant": "Create a banking assistant focused on helping customers with their banking needs. Help with account inquiries, banking products and services, transaction information, and general banking guidance. Prioritize accuracy and security while providing clear and helpful responses to banking-related questions." "Blog Assistant": "Create a blog writer assistant with agents for researching, compiling, outlining and writing the blog. The research agent will research the topic and compile the information. The outline agent will write bullet points for the blog post. The writing agent will expand upon the outline and write the blog post. The blog post should be 1000 words or more.",
} }

View file

@ -92,9 +92,11 @@ export function convertWorkflowToAgenticAPI(workflow: z.infer<typeof Workflow>):
ragDataSources: agent.ragDataSources, ragDataSources: agent.ragDataSources,
ragK: agent.ragK, ragK: agent.ragK,
ragReturnType: agent.ragReturnType, ragReturnType: agent.ragReturnType,
outputVisibility: agent.outputVisibility,
tools: entities.filter(e => e.type == 'tool').map(e => e.name), tools: entities.filter(e => e.type == 'tool').map(e => e.name),
prompts: entities.filter(e => e.type == 'prompt').map(e => e.name), prompts: entities.filter(e => e.type == 'prompt').map(e => e.name),
connectedAgents: entities.filter(e => e.type === 'agent').map(e => e.name), connectedAgents: entities.filter(e => e.type === 'agent').map(e => e.name),
maxCallsPerParentAgent: agent.maxCallsPerParentAgent,
}; };
return agenticAgent; return agenticAgent;
}), }),

View file

@ -17,7 +17,9 @@ export const WorkflowAgent = z.object({
ragDataSources: z.array(z.string()).optional(), ragDataSources: z.array(z.string()).optional(),
ragReturnType: z.union([z.literal('chunks'), z.literal('content')]).default('chunks'), ragReturnType: z.union([z.literal('chunks'), z.literal('content')]).default('chunks'),
ragK: z.number().default(3), ragK: z.number().default(3),
outputVisibility: z.union([z.literal('user_facing'), z.literal('internal')]).default('user_facing').optional(),
controlType: z.union([z.literal('retain'), z.literal('relinquish_to_parent'), z.literal('relinquish_to_start')]).default('retain').describe('Whether this agent retains control after a turn, relinquishes to the parent agent, or relinquishes to the start agent'), controlType: z.union([z.literal('retain'), z.literal('relinquish_to_parent'), z.literal('relinquish_to_start')]).default('retain').describe('Whether this agent retains control after a turn, relinquishes to the parent agent, or relinquishes to the start agent'),
maxCallsPerParentAgent: z.number().default(3).describe('Maximum number of times this agent can be called by a parent agent in a single turn').optional(),
}); });
export const WorkflowPrompt = z.object({ export const WorkflowPrompt = z.object({
name: z.string(), name: z.string(),

View file

@ -42,7 +42,7 @@ export function useCopilot({ projectId, workflow, context }: UseCopilotParams):
try { try {
const res = await getCopilotResponseStream(projectId, messages, workflow, context || null); const res = await getCopilotResponseStream(projectId, messages, workflow, context || null);
const eventSource = new EventSource(`/api/v1/copilot-stream-response/${res.streamId}`); const eventSource = new EventSource(`/api/copilot-stream-response/${res.streamId}`);
eventSource.onmessage = (event) => { eventSource.onmessage = (event) => {
try { try {

View file

@ -4,7 +4,7 @@ import { AgenticAPITool } from "../../../lib/types/agents_api_types";
import { WorkflowPrompt, WorkflowAgent, Workflow } from "../../../lib/types/workflow_types"; import { WorkflowPrompt, WorkflowAgent, Workflow } from "../../../lib/types/workflow_types";
import { DataSource } from "../../../lib/types/datasource_types"; import { DataSource } from "../../../lib/types/datasource_types";
import { z } from "zod"; import { z } from "zod";
import { PlusIcon, Sparkles, X as XIcon, ChevronDown, ChevronRight, Trash2 } from "lucide-react"; import { PlusIcon, Sparkles, X as XIcon, ChevronDown, ChevronRight, Trash2, Maximize2, Minimize2 } from "lucide-react";
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { usePreviewModal } from "../workflow/preview-modal"; import { usePreviewModal } from "../workflow/preview-modal";
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, SelectItem } from "@heroui/react"; import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, SelectItem } from "@heroui/react";
@ -29,6 +29,9 @@ const sectionHeaderStyles = "text-xs font-medium uppercase tracking-wider text-g
// Common textarea styles // Common textarea styles
const textareaStyles = "rounded-lg p-3 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-750 focus:shadow-inner focus:ring-2 focus:ring-indigo-500/20 dark:focus:ring-indigo-400/20 placeholder:text-gray-400 dark:placeholder:text-gray-500"; const textareaStyles = "rounded-lg p-3 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-750 focus:shadow-inner focus:ring-2 focus:ring-indigo-500/20 dark:focus:ring-indigo-400/20 placeholder:text-gray-400 dark:placeholder:text-gray-500";
// Add this type definition after the imports
type TabType = 'instructions' | 'examples' | 'configurations';
export function AgentConfig({ export function AgentConfig({
projectId, projectId,
workflow, workflow,
@ -56,9 +59,12 @@ export function AgentConfig({
}) { }) {
const [isAdvancedConfigOpen, setIsAdvancedConfigOpen] = useState(false); const [isAdvancedConfigOpen, setIsAdvancedConfigOpen] = useState(false);
const [showGenerateModal, setShowGenerateModal] = useState(false); const [showGenerateModal, setShowGenerateModal] = useState(false);
const [isInstructionsMaximized, setIsInstructionsMaximized] = useState(false);
const [isExamplesMaximized, setIsExamplesMaximized] = useState(false);
const { showPreview } = usePreviewModal(); const { showPreview } = usePreviewModal();
const [localName, setLocalName] = useState(agent.name); const [localName, setLocalName] = useState(agent.name);
const [nameError, setNameError] = useState<string | null>(null); const [nameError, setNameError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<TabType>('instructions');
useEffect(() => { useEffect(() => {
setLocalName(agent.name); setLocalName(agent.name);
@ -71,6 +77,23 @@ export function AgentConfig({
} }
}, [agent.controlType, agent, handleUpdate]); }, [agent.controlType, agent, handleUpdate]);
// Add effect to handle escape key
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
if (isInstructionsMaximized) {
setIsInstructionsMaximized(false);
}
if (isExamplesMaximized) {
setIsExamplesMaximized(false);
}
}
};
window.addEventListener('keydown', handleEscape);
return () => window.removeEventListener('keydown', handleEscape);
}, [isInstructionsMaximized, isExamplesMaximized]);
const validateName = (value: string) => { const validateName = (value: string) => {
if (value.length === 0) { if (value.length === 0) {
setNameError("Name cannot be empty"); setNameError("Name cannot be empty");
@ -118,15 +141,223 @@ export function AgentConfig({
variant="secondary" variant="secondary"
size="sm" size="sm"
onClick={handleClose} onClick={handleClose}
startContent={<XIcon className="w-4 h-4" />} showHoverContent={true}
aria-label="Close agent config" hoverContent="Close"
> >
Close <XIcon className="w-4 h-4" />
</CustomButton> </CustomButton>
</div> </div>
} }
> >
<div className="flex flex-col gap-6 p-4"> <div className="flex flex-col gap-6 p-4 h-[calc(100vh-100px)] min-h-0 flex-1">
{/* Tabs */}
<div className="flex border-b border-gray-200 dark:border-gray-700">
{(['instructions', 'examples', 'configurations'] as TabType[]).map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={clsx(
"px-4 py-2 text-sm font-medium transition-colors relative",
activeTab === tab
? "text-indigo-600 dark:text-indigo-400 after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-indigo-500 dark:after:bg-indigo-400"
: "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
)}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
</button>
))}
</div>
{/* Tab Content */}
<div className="mt-4 flex-1 flex flex-col min-h-0 h-0">
{activeTab === 'instructions' && (
<>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<label className={sectionHeaderStyles}>
Instructions
</label>
<CustomButton
variant="secondary"
size="sm"
onClick={() => setIsInstructionsMaximized(!isInstructionsMaximized)}
showHoverContent={true}
hoverContent={isInstructionsMaximized ? "Minimize" : "Maximize"}
>
{isInstructionsMaximized ? (
<Minimize2 className="w-4 h-4" />
) : (
<Maximize2 className="w-4 h-4" />
)}
</CustomButton>
</div>
<CustomButton
variant="primary"
size="sm"
onClick={() => setShowGenerateModal(true)}
startContent={<Sparkles className="w-4 h-4" />}
>
Generate
</CustomButton>
</div>
{isInstructionsMaximized ? (
<div className="fixed inset-0 z-50 bg-white dark:bg-gray-900">
<div className="h-full flex flex-col">
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-2">
<label className={sectionHeaderStyles}>
Instructions
</label>
<CustomButton
variant="secondary"
size="sm"
onClick={() => setIsInstructionsMaximized(false)}
showHoverContent={true}
hoverContent="Minimize"
>
<Minimize2 className="w-4 h-4" />
</CustomButton>
</div>
<CustomButton
variant="primary"
size="sm"
onClick={() => setShowGenerateModal(true)}
startContent={<Sparkles className="w-4 h-4" />}
>
Generate
</CustomButton>
</div>
<div className="flex-1 overflow-hidden p-4">
<EditableField
key="instructions-maximized"
value={agent.instructions}
onChange={(value) => {
handleUpdate({
...agent,
instructions: value
});
}}
markdown
multiline
mentions
mentionsAtValues={atMentions}
showSaveButton={true}
showDiscardButton={true}
className="h-full min-h-0 overflow-auto"
/>
</div>
</div>
</div>
) : (
<EditableField
key="instructions"
value={agent.instructions}
onChange={(value) => {
handleUpdate({
...agent,
instructions: value
});
}}
markdown
multiline
mentions
mentionsAtValues={atMentions}
showSaveButton={true}
showDiscardButton={true}
className="h-full min-h-0 overflow-auto"
/>
)}
</>
)}
{activeTab === 'examples' && (
<>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<label className={sectionHeaderStyles}>
Examples
</label>
<CustomButton
variant="secondary"
size="sm"
onClick={() => setIsExamplesMaximized(!isExamplesMaximized)}
showHoverContent={true}
hoverContent={isExamplesMaximized ? "Minimize" : "Maximize"}
>
{isExamplesMaximized ? (
<Minimize2 className="w-4 h-4" />
) : (
<Maximize2 className="w-4 h-4" />
)}
</CustomButton>
</div>
</div>
{isExamplesMaximized ? (
<div className="fixed inset-0 z-50 bg-white dark:bg-gray-900">
<div className="h-full flex flex-col">
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-2">
<label className={sectionHeaderStyles}>
Examples
</label>
<CustomButton
variant="secondary"
size="sm"
onClick={() => setIsExamplesMaximized(false)}
showHoverContent={true}
hoverContent="Minimize"
>
<Minimize2 className="w-4 h-4" />
</CustomButton>
</div>
</div>
<div className="flex-1 overflow-hidden p-4">
<EditableField
key="examples-maximized"
value={agent.examples || ""}
onChange={(value) => {
handleUpdate({
...agent,
examples: value
});
}}
placeholder="Enter examples for this agent"
markdown
multiline
mentions
mentionsAtValues={atMentions}
showSaveButton={true}
showDiscardButton={true}
className="h-full min-h-0 overflow-auto"
/>
</div>
</div>
</div>
) : (
<EditableField
key="examples"
value={agent.examples || ""}
onChange={(value) => {
handleUpdate({
...agent,
examples: value
});
}}
placeholder="Enter examples for this agent"
markdown
multiline
mentions
mentionsAtValues={atMentions}
showSaveButton={true}
showDiscardButton={true}
className="h-full min-h-0 overflow-auto"
/>
)}
</>
)}
{activeTab === 'configurations' && (
<div className="space-y-6">
{!agent.locked && ( {!agent.locked && (
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
@ -187,59 +418,34 @@ export function AgentConfig({
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center">
<label className={sectionHeaderStyles}> <label className={sectionHeaderStyles}>
Instructions Agent Type
</label> </label>
<CustomButton <div className="relative ml-2 group">
variant="primary" <Info
size="sm" className="w-4 h-4 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer transition-colors"
onClick={() => setShowGenerateModal(true)}
startContent={<Sparkles className="w-4 h-4" />}
>
Generate
</CustomButton>
</div>
<EditableField
key="instructions"
value={agent.instructions}
onChange={(value) => {
handleUpdate({
...agent,
instructions: value
});
}}
markdown
multiline
mentions
mentionsAtValues={atMentions}
showSaveButton={true}
showDiscardButton={true}
className="border border-gray-200 dark:border-gray-700 rounded-lg focus-within:ring-2 focus-within:ring-indigo-500/20 dark:focus-within:ring-indigo-400/20"
/> />
<div className="absolute bottom-full left-0 mb-2 p-3 w-80 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs invisible group-hover:visible z-50">
<div className="mb-1 font-medium">Agent Types</div>
Conversation agents&apos; responses are user-facing. You can use conversation agents for multi-turn conversations with users.
<br />
<br />
Task agents&apos; responses are internal and available to other agents. You can use them to build pipelines and DAGs within workflows. E.g. Conversation Agent {'->'} Task Agent {'->'} Task Agent.
<div className="absolute h-2 w-2 bg-white dark:bg-gray-800 transform rotate-45 -bottom-1 left-4 border-r border-b border-gray-200 dark:border-gray-700"></div>
</div> </div>
</div>
<div className="space-y-4"> </div>
<label className={sectionHeaderStyles}> <CustomDropdown
Examples value={agent.outputVisibility}
</label> options={[
<EditableField { key: "user_facing", label: "Conversation Agent" },
key="examples" { key: "internal", label: "Task Agent" }
value={agent.examples || ""} ]}
onChange={(value) => { onChange={(value) => handleUpdate({
handleUpdate({
...agent, ...agent,
examples: value outputVisibility: value as z.infer<typeof WorkflowAgent>['outputVisibility']
}); })}
}}
placeholder="Enter examples for this agent"
markdown
multiline
mentions
mentionsAtValues={atMentions}
showSaveButton={true}
showDiscardButton={true}
className="border border-gray-200 dark:border-gray-700 rounded-lg focus-within:ring-2 focus-within:ring-indigo-500/20 dark:focus-within:ring-indigo-400/20"
/> />
</div> </div>
@ -428,14 +634,47 @@ export function AgentConfig({
</div> </div>
</div> </div>
</div> </div>
<div className="w-full">
<Input <Input
value={agent.model} value={agent.model}
onChange={(e) => handleUpdate({ onChange={(e) => handleUpdate({
...agent, ...agent,
model: e.target.value as z.infer<typeof WorkflowAgent>['model'] model: e.target.value as z.infer<typeof WorkflowAgent>['model']
})} })}
className="w-full max-w-64"
/> />
</div> </div>
</div>
<div className="space-y-4">
<div className="flex items-center">
<label className={sectionHeaderStyles}>
Max calls from parent agent per turn
</label>
<div className="relative ml-2 group">
<Info
className="w-4 h-4 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer transition-colors"
/>
<div className="absolute bottom-full left-0 mb-2 p-3 w-80 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs invisible group-hover:visible z-50">
<div className="mb-1 font-medium">Max Calls Configuration</div>
This setting limits how many times a parent agent can call this agent in a single turn, to prevent infinite loops.
<div className="absolute h-2 w-2 bg-white dark:bg-gray-800 transform rotate-45 -bottom-1 left-4 border-r border-b border-gray-200 dark:border-gray-700"></div>
</div>
</div>
</div>
<div className="w-full">
<Input
type="number"
min="1"
value={agent.maxCallsPerParentAgent || 3}
onChange={(e) => handleUpdate({
...agent,
maxCallsPerParentAgent: parseInt(e.target.value)
})}
className="w-full max-w-24"
/>
</div>
</div>
{USE_TRANSFER_CONTROL_OPTIONS && ( {USE_TRANSFER_CONTROL_OPTIONS && (
<div className="space-y-4"> <div className="space-y-4">
@ -456,6 +695,9 @@ export function AgentConfig({
/> />
</div> </div>
)} )}
</div>
)}
</div>
<PreviewModalProvider> <PreviewModalProvider>
<GenerateInstructionsModal <GenerateInstructionsModal

View file

@ -61,11 +61,10 @@ export function PromptConfig({
variant="secondary" variant="secondary"
size="sm" size="sm"
onClick={handleClose} onClick={handleClose}
startContent={<XIcon className="w-4 h-4" />} showHoverContent={true}
aria-label="Close prompt config" hoverContent="Close"
className="transition-colors"
> >
Close <XIcon className="w-4 h-4" />
</Button> </Button>
</div> </div>
} }

View file

@ -256,10 +256,10 @@ export function ToolConfig({
variant="secondary" variant="secondary"
size="sm" size="sm"
onClick={handleClose} onClick={handleClose}
startContent={<XIcon className="w-4 h-4" />} showHoverContent={true}
aria-label="Close tool config" hoverContent="Close"
> >
Close <XIcon className="w-4 h-4" />
</Button> </Button>
</div> </div>
} }

View file

@ -11,7 +11,7 @@ import { apiV1 } from "rowboat-shared";
import { TestProfile } from "@/app/lib/types/testing_types"; import { TestProfile } from "@/app/lib/types/testing_types";
import { WithStringId } from "@/app/lib/types/types"; import { WithStringId } from "@/app/lib/types/types";
import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector"; import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector";
import { CheckIcon, CopyIcon, PlusIcon, UserIcon, InfoIcon } from "lucide-react"; import { CheckIcon, CopyIcon, PlusIcon, UserIcon, InfoIcon, BugIcon, BugOffIcon } from "lucide-react";
import { USE_TESTING_FEATURE } from "@/app/lib/feature_flags"; import { USE_TESTING_FEATURE } from "@/app/lib/feature_flags";
import { clsx } from "clsx"; import { clsx } from "clsx";
@ -39,6 +39,7 @@ export function App({
const [counter, setCounter] = useState<number>(0); const [counter, setCounter] = useState<number>(0);
const [testProfile, setTestProfile] = useState<WithStringId<z.infer<typeof TestProfile>> | null>(null); const [testProfile, setTestProfile] = useState<WithStringId<z.infer<typeof TestProfile>> | null>(null);
const [systemMessage, setSystemMessage] = useState<string>(defaultSystemMessage); const [systemMessage, setSystemMessage] = useState<string>(defaultSystemMessage);
const [showDebugMessages, setShowDebugMessages] = useState<boolean>(true);
const [chat, setChat] = useState<z.infer<typeof PlaygroundChat>>({ const [chat, setChat] = useState<z.infer<typeof PlaygroundChat>>({
projectId, projectId,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
@ -116,6 +117,20 @@ export function App({
> >
<PlusIcon className="w-4 h-4" /> <PlusIcon className="w-4 h-4" />
</Button> </Button>
<Button
variant="primary"
size="sm"
onClick={() => setShowDebugMessages(!showDebugMessages)}
className={showDebugMessages ? "bg-blue-50 text-blue-700 hover:bg-blue-100" : "bg-gray-50 text-gray-500 hover:bg-gray-100"}
showHoverContent={true}
hoverContent={showDebugMessages ? "Hide debug messages" : "Show debug messages"}
>
{showDebugMessages ? (
<BugIcon className="w-4 h-4" />
) : (
<BugOffIcon className="w-4 h-4" />
)}
</Button>
</div> </div>
} }
rightActions={ rightActions={
@ -146,9 +161,6 @@ export function App({
</Button> </Button>
</div> </div>
} }
className={clsx(
isInitialState && "opacity-50 transition-opacity duration-300"
)}
onClick={onPanelClick} onClick={onPanelClick}
> >
<ProfileSelector <ProfileSelector
@ -172,6 +184,7 @@ export function App({
mcpServerUrls={mcpServerUrls} mcpServerUrls={mcpServerUrls}
toolWebhookUrl={toolWebhookUrl} toolWebhookUrl={toolWebhookUrl}
onCopyClick={(fn) => { getCopyContentRef.current = fn; }} onCopyClick={(fn) => { getCopyContentRef.current = fn; }}
showDebugMessages={showDebugMessages}
/> />
</div> </div>
</Panel> </Panel>

View file

@ -28,6 +28,7 @@ export function Chat({
mcpServerUrls, mcpServerUrls,
toolWebhookUrl, toolWebhookUrl,
onCopyClick, onCopyClick,
showDebugMessages = true,
}: { }: {
chat: z.infer<typeof PlaygroundChat>; chat: z.infer<typeof PlaygroundChat>;
projectId: string; projectId: string;
@ -40,6 +41,7 @@ export function Chat({
mcpServerUrls: Array<z.infer<typeof MCPServer>>; mcpServerUrls: Array<z.infer<typeof MCPServer>>;
toolWebhookUrl: string; toolWebhookUrl: string;
onCopyClick: (fn: () => string) => void; onCopyClick: (fn: () => string) => void;
showDebugMessages?: boolean;
}) { }) {
const [messages, setMessages] = useState<z.infer<typeof apiV1.ChatMessage>[]>(chat.messages); const [messages, setMessages] = useState<z.infer<typeof apiV1.ChatMessage>[]>(chat.messages);
const [loadingAssistantResponse, setLoadingAssistantResponse] = useState<boolean>(false); const [loadingAssistantResponse, setLoadingAssistantResponse] = useState<boolean>(false);
@ -164,7 +166,7 @@ export function Chat({
return; return;
} }
eventSource = new EventSource(`/api/v1/stream-response/${streamId}`); eventSource = new EventSource(`/api/stream-response/${streamId}`);
eventSource.addEventListener("message", (event) => { eventSource.addEventListener("message", (event) => {
if (ignore) { if (ignore) {
@ -285,6 +287,7 @@ export function Chat({
systemMessage={systemMessage} systemMessage={systemMessage}
onSystemMessageChange={onSystemMessageChange} onSystemMessageChange={onSystemMessageChange}
showSystemMessage={false} showSystemMessage={false}
showDebugMessages={showDebugMessages}
/> />
</div> </div>
</div> </div>

View file

@ -6,22 +6,21 @@ import { Workflow } from "@/app/lib/types/workflow_types";
import { WorkflowTool } from "@/app/lib/types/workflow_types"; import { WorkflowTool } from "@/app/lib/types/workflow_types";
import MarkdownContent from "@/app/lib/components/markdown-content"; import MarkdownContent from "@/app/lib/components/markdown-content";
import { apiV1 } from "rowboat-shared"; import { apiV1 } from "rowboat-shared";
import { MessageSquareIcon, EllipsisIcon, CircleCheckIcon, ChevronRightIcon, ChevronDownIcon, XIcon } from "lucide-react"; import { MessageSquareIcon, EllipsisIcon, CircleCheckIcon, ChevronRightIcon, ChevronDownIcon, ChevronUpIcon, XIcon, PlusIcon } from "lucide-react";
import { TestProfile } from "@/app/lib/types/testing_types"; import { TestProfile } from "@/app/lib/types/testing_types";
import { ProfileContextBox } from "./profile-context-box"; import { ProfileContextBox } from "./profile-context-box";
function UserMessage({ content }: { content: string }) { function UserMessage({ content }: { content: string }) {
return ( return (
<div className="self-end flex flex-col items-end gap-1"> <div className="self-end flex flex-col items-end gap-1 mt-5 mb-8">
<div className="text-gray-500 dark:text-gray-400 text-xs"> <div className="text-gray-500 dark:text-gray-400 text-xs">
User User
</div> </div>
<div className="max-w-[85%] inline-block"> <div className="max-w-[85%] inline-block">
<div className="bg-blue-50 dark:bg-[#1e2023] px-4 py-2.5 <div className="bg-blue-100 dark:bg-blue-900/40 px-4 py-2.5
rounded-2xl rounded-br-lg text-sm leading-relaxed rounded-2xl rounded-br-lg text-sm leading-relaxed
text-gray-700 dark:text-gray-200 text-gray-800 dark:text-blue-100
border border-blue-100 dark:border-[#2a2d31] border-none shadow-sm animate-slideUpAndFade">
shadow-sm animate-slideUpAndFade">
<div className="text-left"> <div className="text-left">
<MarkdownContent content={content} /> <MarkdownContent content={content} />
</div> </div>
@ -31,52 +30,77 @@ function UserMessage({ content }: { content: string }) {
); );
} }
function InternalAssistantMessage({ content, sender, latency }: { content: string, sender: string | null | undefined, latency: number }) { function InternalAssistantMessage({ content, sender, latency, delta }: { content: string, sender: string | null | undefined, latency: number, delta: number }) {
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
// Show plus icon and duration
const deltaDisplay = (
<span className="inline-flex items-center text-gray-400 dark:text-gray-500">
+{Math.round(delta / 1000)}s
</span>
);
// Get first line preview
const firstLine = content.split('\n')[0].trim();
const preview = firstLine.length > 50 ? firstLine.substring(0, 50) + '...' : firstLine;
return ( return (
<div className="self-start flex flex-col gap-1"> <div className="self-start flex flex-col gap-1 my-5">
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1">
{sender ?? 'Assistant'}
</div>
<div className={expanded ? 'max-w-[85%] inline-block' : 'inline-block'}>
<div className={expanded
? 'bg-gray-50 dark:bg-zinc-800 px-4 py-2.5 rounded-2xl rounded-bl-lg text-sm leading-relaxed text-gray-700 dark:text-gray-200 border-none shadow-sm animate-slideUpAndFade flex flex-col items-stretch'
: 'bg-gray-50 dark:bg-zinc-800 px-4 py-2.5 rounded-2xl rounded-bl-lg text-sm leading-relaxed text-gray-700 dark:text-gray-200 border-none shadow-sm animate-slideUpAndFade w-fit'}>
{!expanded ? ( {!expanded ? (
<button className="flex items-center text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 gap-1 group" <div className="flex flex-col gap-2">
onClick={() => setExpanded(true)}> <div className="text-gray-700 dark:text-gray-200">
<MessageSquareIcon size={16} /> {preview}
<EllipsisIcon size={16} /> </div>
<span className="text-xs">Show debug message</span> <div className="flex justify-between items-center gap-6">
<button className="flex items-center gap-1 text-xs text-gray-600 dark:text-gray-300 hover:underline self-start" onClick={() => setExpanded(true)}>
<ChevronDownIcon size={16} />
Show internal message
</button> </button>
<div className="text-right text-xs">
{deltaDisplay}
</div>
</div>
</div>
) : ( ) : (
<> <>
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1 flex items-center justify-between"> <div className="text-left mb-2">
<span>{sender ?? 'Assistant'}</span> <MarkdownContent content={content} />
<button className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
onClick={() => setExpanded(false)}>
<XIcon size={16} />
</button>
</div> </div>
<div className="max-w-[85%] inline-block"> <div className="flex justify-between items-center gap-6 mt-2">
<div className="border border-gray-200 dark:border-gray-700 border-dashed <button className="flex items-center gap-1 text-xs text-gray-600 dark:text-gray-300 hover:underline self-start" onClick={() => setExpanded(false)}>
px-4 py-2.5 rounded-2xl rounded-bl-lg text-sm <ChevronUpIcon size={16} />
text-gray-700 dark:text-gray-200 shadow-sm"> Hide internal message
<pre className="whitespace-pre-wrap">{content}</pre> </button>
<div className="text-right text-xs">
{deltaDisplay}
</div> </div>
</div> </div>
</> </>
)} )}
</div> </div>
</div>
</div>
); );
} }
function AssistantMessage({ content, sender, latency }: { content: string, sender: string | null | undefined, latency: number }) { function AssistantMessage({ content, sender, latency }: { content: string, sender: string | null | undefined, latency: number }) {
return ( return (
<div className="self-start flex flex-col gap-1"> <div className="self-start flex flex-col gap-1 my-5">
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1"> <div className="text-gray-500 dark:text-gray-400 text-xs pl-1">
{sender ?? 'Assistant'} {sender ?? 'Assistant'}
</div> </div>
<div className="max-w-[85%] inline-block"> <div className="max-w-[85%] inline-block">
<div className="bg-gray-50 dark:bg-[#1e2023] px-4 py-2.5 <div className="bg-purple-50 dark:bg-purple-900/30 px-4 py-2.5
rounded-2xl rounded-bl-lg text-sm leading-relaxed rounded-2xl rounded-bl-lg text-sm leading-relaxed
text-gray-700 dark:text-gray-200 text-gray-800 dark:text-purple-100
border border-gray-200 dark:border-[#2a2d31] border-none shadow-sm animate-slideUpAndFade">
shadow-sm animate-slideUpAndFade">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="text-left"> <div className="text-left">
<MarkdownContent content={content} /> <MarkdownContent content={content} />
@ -93,15 +117,11 @@ function AssistantMessage({ content, sender, latency }: { content: string, sende
function AssistantMessageLoading() { function AssistantMessageLoading() {
return ( return (
<div className="self-start flex flex-col gap-1"> <div className="self-start flex flex-col gap-1 my-5">
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1">
Assistant
</div>
<div className="max-w-[85%] inline-block"> <div className="max-w-[85%] inline-block">
<div className="bg-gray-50 dark:bg-gray-800 px-4 py-2.5 <div className="bg-purple-50 dark:bg-purple-900/30 px-4 py-2.5
rounded-2xl rounded-bl-lg rounded-2xl rounded-bl-lg
border border-gray-200 dark:border-gray-700 border-none shadow-sm animate-slideUpAndFade min-h-[2.5rem] flex items-center">
shadow-sm dark:shadow-gray-950/20 animate-pulse min-h-[2.5rem] flex items-center">
<Spinner size="sm" className="ml-2" /> <Spinner size="sm" className="ml-2" />
</div> </div>
</div> </div>
@ -118,6 +138,7 @@ function ToolCalls({
workflow, workflow,
testProfile = null, testProfile = null,
systemMessage, systemMessage,
delta
}: { }: {
toolCalls: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls']; toolCalls: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'];
results: Record<string, z.infer<typeof apiV1.ToolMessage>>; results: Record<string, z.infer<typeof apiV1.ToolMessage>>;
@ -127,6 +148,7 @@ function ToolCalls({
workflow: z.infer<typeof Workflow>; workflow: z.infer<typeof Workflow>;
testProfile: z.infer<typeof TestProfile> | null; testProfile: z.infer<typeof TestProfile> | null;
systemMessage: string | undefined; systemMessage: string | undefined;
delta: number;
}) { }) {
return <div className="flex flex-col gap-4"> return <div className="flex flex-col gap-4">
{toolCalls.map(toolCall => { {toolCalls.map(toolCall => {
@ -136,6 +158,7 @@ function ToolCalls({
result={results[toolCall.id]} result={results[toolCall.id]}
sender={sender} sender={sender}
workflow={workflow} workflow={workflow}
delta={delta}
/> />
})} })}
</div>; </div>;
@ -146,11 +169,13 @@ function ToolCall({
result, result,
sender, sender,
workflow, workflow,
delta
}: { }: {
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number]; toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
result: z.infer<typeof apiV1.ToolMessage> | undefined; result: z.infer<typeof apiV1.ToolMessage> | undefined;
sender: string | null | undefined; sender: string | null | undefined;
workflow: z.infer<typeof Workflow>; workflow: z.infer<typeof Workflow>;
delta: number;
}) { }) {
let matchingWorkflowTool: z.infer<typeof WorkflowTool> | undefined; let matchingWorkflowTool: z.infer<typeof WorkflowTool> | undefined;
for (const tool of workflow.tools) { for (const tool of workflow.tools) {
@ -163,45 +188,61 @@ function ToolCall({
if (toolCall.function.name.startsWith('transfer_to_')) { if (toolCall.function.name.startsWith('transfer_to_')) {
return <TransferToAgentToolCall return <TransferToAgentToolCall
result={result} result={result}
sender={sender} sender={sender ?? ''}
delta={delta}
/>; />;
} }
return <ClientToolCall return <ClientToolCall
toolCall={toolCall} toolCall={toolCall}
result={result} result={result}
sender={sender} sender={sender ?? ''}
workflow={workflow}
delta={delta}
/>; />;
} }
function TransferToAgentToolCall({ function TransferToAgentToolCall({
result: availableResult, result: availableResult,
sender, sender,
delta
}: { }: {
result: z.infer<typeof apiV1.ToolMessage> | undefined; result: z.infer<typeof apiV1.ToolMessage> | undefined;
sender: string | null | undefined; sender: string | null | undefined;
delta: number;
}) { }) {
const typedResult = availableResult ? JSON.parse(availableResult.content) as { assistant: string } : undefined; const typedResult = availableResult ? JSON.parse(availableResult.content) as { assistant: string } : undefined;
if (!typedResult) { if (!typedResult) {
return <></>; return <></>;
} }
const deltaDisplay = (
return <div className="flex gap-1 items-center text-gray-500 text-sm justify-center"> <span className="inline-flex items-center text-gray-400 dark:text-gray-500">
<div>{sender}</div> +{Math.round(delta / 1000)}s
<svg className="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"> </span>
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M19 12H5m14 0-4 4m4-4-4-4" /> );
</svg> return (
<div>{typedResult.assistant}</div> <div className="flex justify-center mb-2">
</div>; <div className="flex items-center gap-2 px-4 py-0.5 rounded-full bg-amber-50 dark:bg-amber-900/20 shadow-sm text-xs">
<span className="text-gray-700 dark:text-gray-200">{sender}</span>
<ChevronRightIcon size={14} className="text-gray-400 dark:text-gray-300" />
<span className="text-gray-700 dark:text-gray-200">{typedResult.assistant}</span>
<span className="ml-2">{deltaDisplay}</span>
</div>
</div>
);
} }
function ClientToolCall({ function ClientToolCall({
toolCall, toolCall,
result: availableResult, result: availableResult,
sender, sender,
workflow,
delta
}: { }: {
toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number]; toolCall: z.infer<typeof apiV1.AssistantMessageWithToolCalls>['tool_calls'][number];
result: z.infer<typeof apiV1.ToolMessage> | undefined; result: z.infer<typeof apiV1.ToolMessage> | undefined;
sender: string | null | undefined; sender: string | null | undefined;
workflow: z.infer<typeof Workflow>;
delta: number;
}) { }) {
return ( return (
<div className="self-start flex flex-col gap-1"> <div className="self-start flex flex-col gap-1">
@ -299,6 +340,7 @@ export function Messages({
systemMessage, systemMessage,
onSystemMessageChange, onSystemMessageChange,
showSystemMessage, showSystemMessage,
showDebugMessages = true,
}: { }: {
projectId: string; projectId: string;
messages: z.infer<typeof apiV1.ChatMessage>[]; messages: z.infer<typeof apiV1.ChatMessage>[];
@ -309,6 +351,7 @@ export function Messages({
systemMessage: string | undefined; systemMessage: string | undefined;
onSystemMessageChange: (message: string) => void; onSystemMessageChange: (message: string) => void;
showSystemMessage: boolean; showSystemMessage: boolean;
showDebugMessages?: boolean;
}) { }) {
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
let lastUserMessageTimestamp = 0; let lastUserMessageTimestamp = 0;
@ -322,36 +365,53 @@ export function Messages({
const isConsecutive = index > 0 && messages[index - 1].role === message.role; const isConsecutive = index > 0 && messages[index - 1].role === message.role;
if (message.role === 'assistant') { if (message.role === 'assistant') {
// the assistant message createdAt is an ISO string timestamp
let latency = new Date(message.createdAt).getTime() - lastUserMessageTimestamp; let latency = new Date(message.createdAt).getTime() - lastUserMessageTimestamp;
// if this is the first message, set the latency to 0
if (!userMessageSeen) { if (!userMessageSeen) {
latency = 0; latency = 0;
} }
if ('tool_calls' in message) {
// First check for tool calls
if ('tool_calls' in message && message.tool_calls) {
// Skip tool calls if debug mode is off
if (!showDebugMessages) {
return null;
}
return ( return (
<ToolCalls <ToolCalls
toolCalls={message.tool_calls} toolCalls={message.tool_calls}
results={toolCallResults} results={toolCallResults}
projectId={projectId} projectId={projectId}
messages={messages} messages={messages}
sender={message.agenticSender} sender={message.agenticSender ?? ''}
workflow={workflow} workflow={workflow}
testProfile={testProfile} testProfile={testProfile}
systemMessage={systemMessage} systemMessage={systemMessage}
delta={latency}
/> />
); );
} }
return message.agenticResponseType === 'internal' ? (
// Then check for internal messages
if (message.agenticResponseType === 'internal') {
// Skip internal messages if debug mode is off
if (!showDebugMessages) {
return null;
}
return (
<InternalAssistantMessage <InternalAssistantMessage
content={message.content} content={message.content ?? ''}
sender={message.agenticSender} sender={message.agenticSender ?? ''}
latency={latency} latency={latency}
delta={latency}
/> />
) : ( );
}
// Finally, regular assistant messages
return (
<AssistantMessage <AssistantMessage
content={message.content} content={message.content ?? ''}
sender={message.agenticSender} sender={message.agenticSender ?? ''}
latency={latency} latency={latency}
/> />
); );
@ -366,6 +426,14 @@ export function Messages({
return null; return null;
}; };
const isAgentTransition = (message: z.infer<typeof apiV1.ChatMessage>) => {
return message.role === 'assistant' && 'tool_calls' in message && Array.isArray(message.tool_calls) && message.tool_calls.some(tc => tc.function.name.startsWith('transfer_to_'));
};
const isAssistantMessage = (message: z.infer<typeof apiV1.ChatMessage>) => {
return message.role === 'assistant' && (!('tool_calls' in message) || !Array.isArray(message.tool_calls) || !message.tool_calls.some(tc => tc.function.name.startsWith('transfer_to_')));
};
if (showSystemMessage) { if (showSystemMessage) {
return ( return (
<ProfileContextBox <ProfileContextBox
@ -378,15 +446,18 @@ export function Messages({
return ( return (
<div className="max-w-[768px] mx-auto"> <div className="max-w-[768px] mx-auto">
<div className="flex flex-col space-y-2"> <div className="flex flex-col">
{messages.map((message, index) => ( {messages.map((message, index) => {
<div const renderedMessage = renderMessage(message, index);
key={index} if (renderedMessage) {
className={`${index > 0 && messages[index - 1].role === message.role ? 'mt-1' : 'mt-4'}`} return (
> <div key={index}>
{renderMessage(message, index)} {renderedMessage}
</div> </div>
))} );
}
return null;
})}
{loadingAssistantResponse && <AssistantMessageLoading />} {loadingAssistantResponse && <AssistantMessageLoading />}
</div> </div>
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />

View file

@ -11,7 +11,7 @@ import { AgentConfig } from "../entities/agent_config";
import { ToolConfig } from "../entities/tool_config"; import { ToolConfig } from "../entities/tool_config";
import { App as ChatApp } from "../playground/app"; import { App as ChatApp } from "../playground/app";
import { z } from "zod"; import { z } from "zod";
import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownSection, DropdownTrigger, Spinner } from "@heroui/react"; import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Spinner, Tooltip } from "@heroui/react";
import { PromptConfig } from "../entities/prompt_config"; import { PromptConfig } from "../entities/prompt_config";
import { EditableField } from "../../../lib/components/editable-field"; import { EditableField } from "../../../lib/components/editable-field";
import { RelativeTime } from "@primer/react"; import { RelativeTime } from "@primer/react";
@ -27,7 +27,7 @@ import { apiV1 } from "rowboat-shared";
import { publishWorkflow, renameWorkflow, saveWorkflow } from "../../../actions/workflow_actions"; import { publishWorkflow, renameWorkflow, saveWorkflow } from "../../../actions/workflow_actions";
import { PublishedBadge } from "./published_badge"; import { PublishedBadge } from "./published_badge";
import { BackIcon, HamburgerIcon, WorkflowIcon } from "../../../lib/components/icons"; import { BackIcon, HamburgerIcon, WorkflowIcon } from "../../../lib/components/icons";
import { CopyIcon, ImportIcon, Layers2Icon, RadioIcon, RedoIcon, ServerIcon, Sparkles, UndoIcon } from "lucide-react"; import { CopyIcon, ImportIcon, Layers2Icon, RadioIcon, RedoIcon, ServerIcon, Sparkles, UndoIcon, RocketIcon, PenLine, AlertTriangle } from "lucide-react";
import { EntityList } from "./entity_list"; import { EntityList } from "./entity_list";
import { McpImportTools } from "./mcp_imports"; import { McpImportTools } from "./mcp_imports";
import { ProductTour } from "@/components/common/product-tour"; import { ProductTour } from "@/components/common/product-tour";
@ -269,6 +269,8 @@ function reducer(state: State, action: Action): State {
ragReturnType: "chunks", ragReturnType: "chunks",
ragK: 3, ragK: 3,
controlType: "retain", controlType: "retain",
outputVisibility: "user_facing",
maxCallsPerParentAgent: 3,
...action.agent ...action.agent
}); });
draft.selection = { draft.selection = {
@ -784,8 +786,10 @@ export function WorkflowEditor({
return <div className="flex flex-col h-full relative"> return <div className="flex flex-col h-full relative">
<div className="shrink-0 flex justify-between items-center pb-6"> <div className="shrink-0 flex justify-between items-center pb-6">
<div className="workflow-version-selector flex items-center gap-1 px-2 text-gray-800 dark:text-gray-100"> <div className="workflow-version-selector flex items-center gap-4 px-2 text-gray-800 dark:text-gray-100">
<WorkflowIcon size={16} /> <WorkflowIcon size={16} />
<Tooltip content="Click to edit">
<div>
<EditableField <EditableField
key={state.present.workflow._id} key={state.present.workflow._id}
value={state.present.workflow?.name || ''} value={state.present.workflow?.name || ''}
@ -794,18 +798,33 @@ export function WorkflowEditor({
className="text-sm font-semibold" className="text-sm font-semibold"
inline={true} inline={true}
/> />
</div>
</Tooltip>
<div className="flex items-center gap-2">
{state.present.publishing && <Spinner size="sm" />} {state.present.publishing && <Spinner size="sm" />}
{isLive && <PublishedBadge />} {isLive && <div className="bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 px-3 py-1.5 rounded-md text-sm font-medium flex items-center gap-2">
<RadioIcon size={16} />
Live
</div>}
{!isLive && <div className="bg-yellow-50 dark:bg-yellow-900/20 text-yellow-600 dark:text-yellow-400 px-3 py-1.5 rounded-md text-sm font-medium flex items-center gap-2">
<PenLine size={16} />
Draft
</div>}
</div>
<Dropdown> <Dropdown>
<DropdownTrigger> <DropdownTrigger>
<button className="p-1 text-gray-400 hover:text-black"> <div>
<HamburgerIcon size={16} /> <Tooltip content="Version Menu">
<button className="p-1.5 text-gray-500 hover:text-gray-800 transition-colors">
<HamburgerIcon size={20} />
</button> </button>
</Tooltip>
</div>
</DropdownTrigger> </DropdownTrigger>
<DropdownMenu <DropdownMenu
disabledKeys={[ disabledKeys={[
...(state.present.pendingChanges ? ['switch', 'clone'] : []), ...(state.present.pendingChanges ? ['switch', 'clone'] : []),
...(isLive ? ['publish', 'mcp'] : []), ...(isLive ? ['mcp'] : []),
]} ]}
onAction={(key) => { onAction={(key) => {
if (key === 'switch') { if (key === 'switch') {
@ -814,15 +833,11 @@ export function WorkflowEditor({
if (key === 'clone') { if (key === 'clone') {
handleCloneVersion(state.present.workflow._id); handleCloneVersion(state.present.workflow._id);
} }
if (key === 'publish') {
handlePublishWorkflow();
}
if (key === 'clipboard') { if (key === 'clipboard') {
handleCopyJSON(); handleCopyJSON();
} }
}} }}
> >
<DropdownSection>
<DropdownItem <DropdownItem
key="switch" key="switch"
startContent={<div className="text-gray-500"><BackIcon size={16} /></div>} startContent={<div className="text-gray-500"><BackIcon size={16} /></div>}
@ -830,9 +845,7 @@ export function WorkflowEditor({
> >
View versions View versions
</DropdownItem> </DropdownItem>
</DropdownSection>
<DropdownSection>
<DropdownItem <DropdownItem
key="clone" key="clone"
startContent={<div className="text-gray-500"><Layers2Icon size={16} /></div>} startContent={<div className="text-gray-500"><Layers2Icon size={16} /></div>}
@ -841,16 +854,6 @@ export function WorkflowEditor({
Clone this version Clone this version
</DropdownItem> </DropdownItem>
<DropdownItem
key="publish"
startContent={<div className="text-indigo-500"><RadioIcon size={16} /></div>}
className="gap-x-2 text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/20"
>
Make version live
</DropdownItem>
</DropdownSection>
<DropdownSection>
<DropdownItem <DropdownItem
key="clipboard" key="clipboard"
startContent={<div className="text-gray-500"><CopyIcon size={16} /></div>} startContent={<div className="text-gray-500"><CopyIcon size={16} /></div>}
@ -858,7 +861,6 @@ export function WorkflowEditor({
> >
Export as JSON Export as JSON
</DropdownItem> </DropdownItem>
</DropdownSection>
</DropdownMenu> </DropdownMenu>
</Dropdown> </Dropdown>
</div> </div>
@ -867,16 +869,28 @@ export function WorkflowEditor({
</div>} </div>}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{isLive && <div className="flex items-center gap-2"> {isLive && <div className="flex items-center gap-2">
<div className="bg-yellow-50 text-yellow-500 px-2 py-1 rounded-md text-sm"> <div className="bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-400 px-3 py-1.5 rounded-md text-sm font-medium flex items-center gap-2">
This version is locked. You cannot make changes. <AlertTriangle size={16} />
This version is locked. You cannot make changes. Changes applied through copilot will<b>not</b>be reflected.
</div> </div>
<Button <Button
variant="bordered" variant="solid"
size="sm" size="md"
onPress={() => handleCloneVersion(state.present.workflow._id)} onPress={() => handleCloneVersion(state.present.workflow._id)}
className="gap-2 px-4 bg-amber-600 hover:bg-amber-700 text-white font-semibold text-sm"
startContent={<Layers2Icon size={16} />}
> >
Clone this version Clone this version
</Button> </Button>
<Button
variant="solid"
size="md"
onPress={() => setShowCopilot(!showCopilot)}
className="gap-2 px-4 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold text-sm"
startContent={showCopilot ? null : <Sparkles size={16} />}
>
{showCopilot ? "Hide Copilot" : "Copilot"}
</Button>
</div>} </div>}
{!isLive && <div className="text-xs text-gray-400"> {!isLive && <div className="text-xs text-gray-400">
{state.present.saving && <div className="flex items-center gap-1"> {state.present.saving && <div className="flex items-center gap-1">
@ -906,12 +920,22 @@ export function WorkflowEditor({
</button> </button>
<Button <Button
variant="solid" variant="solid"
size="lg" size="md"
onPress={() => setShowCopilot(!showCopilot)} onPress={handlePublishWorkflow}
className="gap-2 px-6 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold text-base" className="gap-2 px-4 bg-green-600 hover:bg-green-700 text-white font-semibold text-sm"
startContent={<Sparkles size={20} />} startContent={<RocketIcon size={16} />}
data-tour-target="deploy"
> >
Copilot Deploy
</Button>
<Button
variant="solid"
size="md"
onPress={() => setShowCopilot(!showCopilot)}
className="gap-2 px-4 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold text-sm"
startContent={showCopilot ? null : <Sparkles size={16} />}
>
{showCopilot ? "Hide Copilot" : "Copilot"}
</Button> </Button>
</>} </>}
</div> </div>

View file

@ -19,6 +19,7 @@ import {
import { getProjectConfig } from "@/app/actions/project_actions"; import { getProjectConfig } from "@/app/actions/project_actions";
import { useTheme } from "@/app/providers/theme-provider"; import { useTheme } from "@/app/providers/theme-provider";
import { USE_TESTING_FEATURE, USE_PRODUCT_TOUR } from '@/app/lib/feature_flags'; import { USE_TESTING_FEATURE, USE_PRODUCT_TOUR } from '@/app/lib/feature_flags';
import { useHelpModal } from "@/app/providers/help-modal-provider";
interface SidebarProps { interface SidebarProps {
projectId: string; projectId: string;
@ -36,6 +37,7 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
const [projectName, setProjectName] = useState<string>("Select Project"); const [projectName, setProjectName] = useState<string>("Select Project");
const isProjectsRoute = pathname === '/projects' || pathname === '/projects/select'; const isProjectsRoute = pathname === '/projects' || pathname === '/projects/select';
const { theme, toggleTheme } = useTheme(); const { theme, toggleTheme } = useTheme();
const { showHelpModal } = useHelpModal();
useEffect(() => { useEffect(() => {
async function fetchProjectName() { async function fetchProjectName() {
@ -79,7 +81,13 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
} }
]; ];
const handleStartTour = () => {
localStorage.removeItem('user_product_tour_completed');
window.location.reload();
};
return ( return (
<>
<aside className={`${collapsed ? 'w-16' : 'w-60'} bg-transparent flex flex-col h-full transition-all duration-300`}> <aside className={`${collapsed ? 'w-16' : 'w-60'} bg-transparent flex flex-col h-full transition-all duration-300`}>
<div className="flex flex-col flex-grow"> <div className="flex flex-col flex-grow">
{!isProjectsRoute && ( {!isProjectsRoute && (
@ -183,12 +191,9 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
{/* Theme and Auth Controls */} {/* Theme and Auth Controls */}
<div className="p-3 border-t border-zinc-100 dark:border-zinc-800 space-y-2"> <div className="p-3 border-t border-zinc-100 dark:border-zinc-800 space-y-2">
{USE_PRODUCT_TOUR && !isProjectsRoute && ( {USE_PRODUCT_TOUR && !isProjectsRoute && (
<Tooltip content={collapsed ? "Take Tour" : ""} showArrow placement="right"> <Tooltip content={collapsed ? "Help" : ""} showArrow placement="right">
<button <button
onClick={() => { onClick={showHelpModal}
localStorage.removeItem('user_product_tour_completed');
window.location.reload();
}}
className={` className={`
w-full rounded-md flex items-center w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200 text-[15px] font-medium transition-all duration-200
@ -196,9 +201,10 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
hover:bg-zinc-100 dark:hover:bg-zinc-800/50 hover:bg-zinc-100 dark:hover:bg-zinc-800/50
text-zinc-600 dark:text-zinc-400 text-zinc-600 dark:text-zinc-400
`} `}
data-tour-target="tour-button"
> >
<HelpCircle size={COLLAPSED_ICON_SIZE} /> <HelpCircle size={COLLAPSED_ICON_SIZE} />
{!collapsed && <span>Take Tour</span>} {!collapsed && <span>Help</span>}
</button> </button>
</Tooltip> </Tooltip>
)} )}
@ -237,5 +243,6 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
</div> </div>
</div> </div>
</aside> </aside>
</>
); );
} }

View file

@ -9,9 +9,10 @@ import { SectionHeading } from "@/components/ui/section-heading";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Submit } from "./submit-button"; import { Submit } from "./submit-button";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { FolderOpenIcon } from "@heroicons/react/24/outline"; import { FolderOpenIcon, InformationCircleIcon } from "@heroicons/react/24/outline";
import { USE_MULTIPLE_PROJECTS } from "@/app/lib/feature_flags"; import { USE_MULTIPLE_PROJECTS } from "@/app/lib/feature_flags";
import { HorizontalDivider } from "@/components/ui/horizontal-divider"; import { HorizontalDivider } from "@/components/ui/horizontal-divider";
import { Tooltip } from "@heroui/react";
// Add glow animation styles // Add glow animation styles
const glowStyles = ` const glowStyles = `
@ -326,7 +327,7 @@ export function CreateProject({ defaultName, onOpenProjectPane, isProjectPaneOpe
</svg> </svg>
} }
> >
Customize an existing example Use an example
</Button> </Button>
{isExamplesDropdownOpen && ( {isExamplesDropdownOpen && (
@ -365,6 +366,14 @@ export function CreateProject({ defaultName, onOpenProjectPane, isProjectPaneOpe
<label className={largeSectionHeaderStyles}> <label className={largeSectionHeaderStyles}>
{selectedTab === TabType.Describe ? '✏️ What do you want to build?' : '✏️ Customize the description'} {selectedTab === TabType.Describe ? '✏️ What do you want to build?' : '✏️ Customize the description'}
</label> </label>
<div className="flex items-center gap-2">
<p className="text-xs text-gray-600 dark:text-gray-400">
In the next step, our AI copilot will create agents for you, complete with mock-tools.
</p>
<Tooltip content={<div>If you already know the specific agents and tools you need, mention them below.<br /><br />Specify &apos;internal agents&apos; for task agents that will not interact with the user and &apos;user-facing agents&apos; for conversational agents that will interact with users.</div>} className="max-w-[560px]">
<InformationCircleIcon className="w-4 h-4 text-indigo-500 hover:text-indigo-600 dark:text-indigo-400 dark:hover:text-indigo-300 cursor-help" />
</Tooltip>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Textarea <Textarea
value={customPrompt} value={customPrompt}

View file

@ -0,0 +1,42 @@
'use client';
import { createContext, useContext, useState, ReactNode } from 'react';
import { HelpModal } from '@/components/common/help-modal';
interface HelpModalContextType {
showHelpModal: () => void;
hideHelpModal: () => void;
}
const HelpModalContext = createContext<HelpModalContextType | undefined>(undefined);
export function HelpModalProvider({ children }: { children: ReactNode }) {
const [isOpen, setIsOpen] = useState(false);
const showHelpModal = () => setIsOpen(true);
const hideHelpModal = () => setIsOpen(false);
const handleStartTour = () => {
localStorage.removeItem('user_product_tour_completed');
window.location.reload();
};
return (
<HelpModalContext.Provider value={{ showHelpModal, hideHelpModal }}>
{children}
<HelpModal
isOpen={isOpen}
onClose={hideHelpModal}
onStartTour={handleStartTour}
/>
</HelpModalContext.Provider>
);
}
export function useHelpModal() {
const context = useContext(HelpModalContext);
if (context === undefined) {
throw new Error('useHelpModal must be used within a HelpModalProvider');
}
return context;
}

View file

@ -0,0 +1,92 @@
import { Button } from "@heroui/react";
import { HelpCircle, BookOpen, MessageCircle } from "lucide-react";
interface HelpModalProps {
isOpen: boolean;
onClose: () => void;
onStartTour: () => void;
}
export function HelpModal({ isOpen, onClose, onStartTour }: HelpModalProps) {
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[100] flex items-center justify-center">
<div className="bg-white dark:bg-zinc-800 rounded-lg shadow-lg p-6 w-[480px] max-w-[90vw] animate-in fade-in duration-200">
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-6">
Need Help?
</h2>
<div className="space-y-4">
<Button
className="w-full justify-start gap-4 text-left py-6 px-4 hover:bg-indigo-50 dark:hover:bg-indigo-500/10 transition-all duration-200 group hover:scale-[1.02] hover:shadow-md"
variant="light"
onPress={onStartTour}
>
<div className="bg-indigo-100 dark:bg-indigo-500/20 p-2 rounded-lg group-hover:bg-indigo-200 dark:group-hover:bg-indigo-500/30 transition-colors">
<HelpCircle className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
</div>
<div>
<div className="font-medium text-base text-gray-900 dark:text-gray-100">Take Product Tour</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Learn about RowBoat&apos;s features
</div>
</div>
</Button>
<a
href="https://docs.rowboatlabs.com/"
target="_blank"
rel="noopener noreferrer"
className="block"
>
<Button
className="w-full justify-start gap-4 text-left py-6 px-4 hover:bg-indigo-50 dark:hover:bg-indigo-500/10 transition-all duration-200 group hover:scale-[1.02] hover:shadow-md"
variant="light"
>
<div className="bg-indigo-100 dark:bg-indigo-500/20 p-2 rounded-lg group-hover:bg-indigo-200 dark:group-hover:bg-indigo-500/30 transition-colors">
<BookOpen className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
</div>
<div>
<div className="font-medium text-base text-gray-900 dark:text-gray-100">View Documentation</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Read our detailed guides
</div>
</div>
</Button>
</a>
<a
href="https://discord.gg/gtbGcqF4"
target="_blank"
rel="noopener noreferrer"
className="block"
>
<Button
className="w-full justify-start gap-4 text-left py-6 px-4 hover:bg-indigo-50 dark:hover:bg-indigo-500/10 transition-all duration-200 group hover:scale-[1.02] hover:shadow-md"
variant="light"
>
<div className="bg-indigo-100 dark:bg-indigo-500/20 p-2 rounded-lg group-hover:bg-indigo-200 dark:group-hover:bg-indigo-500/30 transition-colors">
<MessageCircle className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
</div>
<div>
<div className="font-medium text-base text-gray-900 dark:text-gray-100">Join Discord</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Get help from the community
</div>
</div>
</Button>
</a>
</div>
<div className="mt-8 flex justify-end">
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 px-4 py-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors"
>
Close
</button>
</div>
</div>
</div>
);
}

View file

@ -12,32 +12,42 @@ const TOUR_STEPS: TourStep[] = [
{ {
target: 'copilot', target: 'copilot',
content: 'Build agents with the help of copilot.\nThis might take a minute.', content: 'Build agents with the help of copilot.\nThis might take a minute.',
title: 'Step 1/6' title: 'Step 1/8'
}, },
{ {
target: 'playground', target: 'playground',
content: 'Test your assistant in the playground.\nDebug tool calls and responses.', content: 'Test your assistant in the playground.\nDebug tool calls and responses.',
title: 'Step 2/6' title: 'Step 2/8'
}, },
{ {
target: 'entity-agents', target: 'entity-agents',
content: 'Manage your agents.\nSpecify instructions, examples and tool usage.', content: 'Manage your agents.\nSpecify instructions, examples and tool usage.',
title: 'Step 3/6' title: 'Step 3/8'
}, },
{ {
target: 'entity-tools', target: 'entity-tools',
content: 'Create your own tools, import MCP tools or use existing ones.\nMock tools for quick testing.', content: 'Create your own tools, import MCP tools or use existing ones.\nMock tools for quick testing.',
title: 'Step 4/6' title: 'Step 4/8'
}, },
{ {
target: 'entity-prompts', target: 'entity-prompts',
content: 'Manage prompts which will be used by agents.\nConfigure greeting message.', content: 'Manage prompts which will be used by agents.\nConfigure greeting message.',
title: 'Step 5/6' title: 'Step 5/8'
}, },
{ {
target: 'settings', target: 'settings',
content: 'Configure project settings\nGet API keys, configure tool webhooks.', content: 'Configure project settings\nGet API keys, configure tool webhooks.',
title: 'Step 6/6' title: 'Step 6/8'
},
{
target: 'deploy',
content: 'Deploy your workflow version to make it live.\nThis will make your workflow available for use via the API and SDK.\n\nLearn more:\n• <a href="https://docs.rowboatlabs.com/using_the_api/" target="_blank" class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">Using the API</a>\n• <a href="https://docs.rowboatlabs.com/using_the_sdk/" target="_blank" class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">Using the SDK</a>',
title: 'Step 7/8'
},
{
target: 'tour-button',
content: 'Come back here anytime to restart the tour.\nStill have questions? See our <a href="https://docs.rowboatlabs.com/" target="_blank" class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">docs</a> or reach out on <a href="https://discord.gg/gtbGcqF4" target="_blank" class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">discord</a>.',
title: 'Step 8/8'
} }
]; ];
@ -222,9 +232,9 @@ export function ProductTour({
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1"> <div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
{TOUR_STEPS[currentStep].title} {TOUR_STEPS[currentStep].title}
</div> </div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3 whitespace-pre-line"> <div className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3 whitespace-pre-line [&>a]:underline"
{TOUR_STEPS[currentStep].content} dangerouslySetInnerHTML={{ __html: TOUR_STEPS[currentStep].content }}
</div> />
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<button <button
onClick={handleSkip} onClick={handleSkip}

View file

@ -1,131 +0,0 @@
import { z } from "zod";
export const WorkflowAgent = z.object({
name: z.string(),
type: z.union([
z.literal('conversation'),
z.literal('post_process'),
z.literal('escalation'),
]),
description: z.string(),
disabled: z.boolean().default(false).optional(),
instructions: z.string(),
examples: z.string().optional(),
model: z.union([
z.literal('gpt-4o'),
z.literal('gpt-4o-mini'),
]),
locked: z.boolean().default(false).describe('Whether this agent is locked and cannot be deleted').optional(),
toggleAble: z.boolean().default(true).describe('Whether this agent can be enabled or disabled').optional(),
global: z.boolean().default(false).describe('Whether this agent is a global agent, in which case it cannot be connected to other agents').optional(),
ragDataSources: z.array(z.string()).optional(),
ragReturnType: z.union([z.literal('chunks'), z.literal('content')]).default('chunks'),
ragK: z.number().default(3),
controlType: z.union([z.literal('retain'), z.literal('relinquish_to_parent'), z.literal('relinquish_to_start')]).default('retain').describe('Whether this agent retains control after a turn, relinquishes to the parent agent, or relinquishes to the start agent'),
});
export const WorkflowPrompt = z.object({
name: z.string(),
type: z.union([
z.literal('base_prompt'),
z.literal('style_prompt'),
z.literal('greeting'),
]),
prompt: z.string(),
});
export const WorkflowTool = z.object({
name: z.string(),
description: z.string(),
mockTool: z.boolean().default(false).optional(),
autoSubmitMockedResponse: z.boolean().default(false).optional(),
mockInstructions: z.string().optional(),
parameters: z.object({
type: z.literal('object'),
properties: z.record(z.object({
type: z.string(),
description: z.string(),
})),
required: z.array(z.string()).optional(),
}),
isMcp: z.boolean().default(false).optional(),
mcpServerName: z.string().optional(),
});
export const Workflow = z.object({
name: z.string().optional(),
agents: z.array(WorkflowAgent),
prompts: z.array(WorkflowPrompt),
tools: z.array(WorkflowTool),
startAgent: z.string(),
createdAt: z.string().datetime(),
lastUpdatedAt: z.string().datetime(),
projectId: z.string(),
});
export const WorkflowTemplate = Workflow
.omit({
projectId: true,
lastUpdatedAt: true,
createdAt: true,
})
.extend({
name: z.string(),
description: z.string(),
});
export const ConnectedEntity = z.object({
type: z.union([z.literal('tool'), z.literal('prompt'), z.literal('agent')]),
name: z.string(),
});
export function sanitizeTextWithMentions(
text: string,
workflow: {
agents: z.infer<typeof WorkflowAgent>[],
tools: z.infer<typeof WorkflowTool>[],
prompts: z.infer<typeof WorkflowPrompt>[],
},
): {
sanitized: string;
entities: z.infer<typeof ConnectedEntity>[];
} {
// Regex to match [@type:name](#type:something) pattern where type is tool/prompt/agent
const mentionRegex = /\[@(tool|prompt|agent):([^\]]+)\]\(#mention\)/g;
const seen = new Set<string>();
// collect entities
const entities = Array
.from(text.matchAll(mentionRegex))
.filter(match => {
if (seen.has(match[0])) {
return false;
}
seen.add(match[0]);
return true;
})
.map(match => {
return {
type: match[1] as 'tool' | 'prompt' | 'agent',
name: match[2],
};
})
.filter(entity => {
seen.add(entity.name);
if (entity.type === 'agent') {
return workflow.agents.some(a => a.name === entity.name);
} else if (entity.type === 'tool') {
return workflow.tools.some(t => t.name === entity.name);
} else if (entity.type === 'prompt') {
return workflow.prompts.some(p => p.name === entity.name);
}
return false;
})
// sanitize text
for (const entity of entities) {
const id = `${entity.type}:${entity.name}`;
const textToReplace = `[@${id}](#mention)`;
text = text.replace(textToReplace, `[@${id}]`);
}
return {
sanitized: text,
entities,
};
}

View file

@ -1,6 +1,5 @@
import traceback import traceback
from quart import Quart, request, jsonify, Response from quart import Quart, request, jsonify, Response
from datetime import datetime
from functools import wraps from functools import wraps
import os import os
import json import json
@ -8,15 +7,19 @@ from hypercorn.config import Config
from hypercorn.asyncio import serve from hypercorn.asyncio import serve
import asyncio import asyncio
from src.graph.core import run_turn, run_turn_streamed from src.graph.core import run_turn_streamed
from src.graph.tools import RAG_TOOL, CLOSE_CHAT_TOOL from src.utils.common import read_json_from_file
from src.utils.common import common_logger, read_json_from_file
from pprint import pprint
logger = common_logger
app = Quart(__name__) app = Quart(__name__)
config = read_json_from_file("./configs/default_config.json") master_config = read_json_from_file("./configs/default_config.json")
print("Master config:", master_config)
# Get environment variables with defaults
ENABLE_TRACING = False
try:
ENABLE_TRACING = os.environ.get('ENABLE_TRACING').lower() == 'true'
except Exception as e:
print(f"Error getting ENABLE_TRACING: {e}, using default of False")
# filter out agent transfer messages using a function # filter out agent transfer messages using a function
def is_agent_transfer_message(msg): def is_agent_transfer_message(msg):
@ -59,8 +62,8 @@ def require_api_key(f):
@app.route("/chat", methods=["POST"]) @app.route("/chat", methods=["POST"])
@require_api_key @require_api_key
async def chat(): async def chat():
logger.info('='*100) print('='*100)
logger.info(f"{'*'*100}Running server mode{'*'*100}") print(f"{'*'*100}Running server mode{'*'*100}")
try: try:
request_data = await request.get_json() request_data = await request.get_json()
print("Request:", json.dumps(request_data)) print("Request:", json.dumps(request_data))
@ -84,7 +87,6 @@ async def chat():
data = request_data data = request_data
messages = [] messages = []
final_state = {} final_state = {}
# tokens_used = 0
async for event_type, event_data in run_turn_streamed( async for event_type, event_data in run_turn_streamed(
messages=input_messages, messages=input_messages,
@ -92,32 +94,31 @@ async def chat():
agent_configs=data.get("agents", []), agent_configs=data.get("agents", []),
tool_configs=data.get("tools", []), tool_configs=data.get("tools", []),
prompt_configs=data.get("prompts", []), prompt_configs=data.get("prompts", []),
start_turn_with_start_agent=config.get("start_turn_with_start_agent", False), start_turn_with_start_agent=master_config.get("start_turn_with_start_agent", False),
state=data.get("state", {}), state=data.get("state", {}),
additional_tool_configs=[RAG_TOOL, CLOSE_CHAT_TOOL], complete_request=data,
complete_request=data enable_tracing=ENABLE_TRACING
): ):
if event_type == 'message': if event_type == 'message':
messages.append(event_data) messages.append(event_data)
elif event_type == 'done': elif event_type == 'done':
final_state = event_data['state'] final_state = event_data['state']
# tokens_used = event_data["tokens_used"]
out = { out = {
"messages": messages, "messages": messages,
"state": final_state, "state": final_state,
} }
logger.info("Output:") print("Output:")
for k, v in out.items(): for k, v in out.items():
logger.info(f"{k}: {v}") print(f"{k}: {v}")
logger.info('*'*100) print('*'*100)
return jsonify(out) return jsonify(out)
except Exception as e: except Exception as e:
print(traceback.format_exc()) print(traceback.format_exc())
logger.error(f"Error: {str(e)}") print(f"Error: {str(e)}")
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
def format_sse(data: dict, event: str = None) -> str: def format_sse(data: dict, event: str = None) -> str:
@ -152,6 +153,7 @@ async def chat_stream():
msg["role"] = "user" msg["role"] = "user"
async def generate(): async def generate():
print("Running generate() in server")
try: try:
async for event_type, event_data in run_turn_streamed( async for event_type, event_data in run_turn_streamed(
messages=input_messages, messages=input_messages,
@ -159,23 +161,20 @@ async def chat_stream():
agent_configs=request_data.get("agents", []), agent_configs=request_data.get("agents", []),
tool_configs=request_data.get("tools", []), tool_configs=request_data.get("tools", []),
prompt_configs=request_data.get("prompts", []), prompt_configs=request_data.get("prompts", []),
start_turn_with_start_agent=config.get("start_turn_with_start_agent", False), start_turn_with_start_agent=master_config.get("start_turn_with_start_agent", False),
state=request_data.get("state", {}), state=request_data.get("state", {}),
additional_tool_configs=[RAG_TOOL, CLOSE_CHAT_TOOL], complete_request=request_data,
complete_request=request_data enable_tracing=ENABLE_TRACING
): ):
if event_type == 'message': if event_type == 'message':
print("Yielding message:")
yield format_sse(event_data, "message") yield format_sse(event_data, "message")
elif event_type == 'done': elif event_type == 'done':
print("Yielding done:")
yield format_sse(event_data, "done") yield format_sse(event_data, "done")
elif event_type == 'error': elif event_type == 'error':
print("Yielding error:") yield format_sse(event_data, " error")
yield format_sse(event_data, "stream_error")
except Exception as e: except Exception as e:
logger.error(f"Streaming error: {str(e)}") print(f"Streaming error: {str(e)}")
yield format_sse({"error": str(e)}, "error") yield format_sse({"error": str(e)}, "error")
return Response(generate(), mimetype='text/event-stream') return Response(generate(), mimetype='text/event-stream')

View file

@ -7,20 +7,16 @@ import logging
from .helpers.access import ( from .helpers.access import (
get_agent_by_name, get_agent_by_name,
get_external_tools, get_external_tools,
get_prompt_by_type get_prompt_by_type,
get_agent_config_by_name
) )
from .helpers.state import ( from .helpers.library_tools import handle_web_search_event
construct_state_from_response from .helpers.control import get_last_agent_name
) from .execute_turn import run_streamed as swarm_run_streamed, get_agents
from .helpers.control import get_latest_assistant_msg, get_latest_non_assistant_messages, get_last_agent_name from .helpers.instructions import add_child_transfer_related_instructions
from .swarm_wrapper import run as swarm_run, run_streamed as swarm_run_streamed, create_response, get_agents from .types import PromptType, outputVisibility, ResponseType
from src.utils.common import common_logger as logger from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
from .types import PromptType
# Create a dedicated logger for swarm wrapper
logger.setLevel(logging.INFO)
print("Logger level set to INFO")
def order_messages(messages): def order_messages(messages):
""" """
@ -52,197 +48,41 @@ def set_sys_message(messages):
if messages[0].get("role") == "system" and messages[0].get("content") == "": if messages[0].get("role") == "system" and messages[0].get("content") == "":
messages[0]["content"] = "You are a helpful assistant." messages[0]["content"] = "You are a helpful assistant."
print("Updated system message: ", messages[0]) print("Updated system message: ", messages[0])
logger.info("Updated system message: ", messages[0])
print("Messages: ", messages)
# logger.info("Messages: ", messages)
return messages return messages
def clean_up_history(agent_data): def add_child_transfer_related_instructions_to_agents(agents):
""" for agent in agents:
Ensures each agent's history is sorted using order_messages. add_child_transfer_related_instructions(agent)
""" return agents
for data in agent_data:
data["history"] = order_messages(data["history"])
return agent_data
def create_final_response(response, turn_messages, tokens_used, all_agents): def add_openai_recommended_instructions_to_agents(agents):
""" for agent in agents:
Constructs the final response data (messages, tokens_used, updated state) that a caller would need. agent.instructions = RECOMMENDED_PROMPT_PREFIX + '\n\n' + agent.instructions
""" return agents
# Ensure response has a messages attribute
if not hasattr(response, 'messages'):
response.messages = []
# Assign the appropriate messages to the response def check_internal_visibility(current_agent):
response.messages = turn_messages """Check if an agent is internal based on its outputVisibility"""
return current_agent.output_visibility == outputVisibility.INTERNAL.value
# Ensure tokens_used is a valid dictionary def add_sender_details_to_messages(messages):
if not isinstance(tokens_used, dict): for msg in messages:
tokens_used = {"total": 0, "prompt": 0, "completion": 0} # Default values if not a dictionary msg['sender'] = msg.get('sender', None)
if msg.get('sender'):
msg['content'] = f"Sender agent: {msg.get('sender')}\nContent: {msg.get('content')}"
return messages
# Ensure response has a tokens_used attribute that's a dictionary def append_messages(messages, accumulated_messages):
if not hasattr(response, 'tokens_used') or not isinstance(response.tokens_used, dict): # Create a set of existing message contents for O(1) lookup
response.tokens_used = {} existing_contents = {msg.get('content') for msg in messages}
response.tokens_used = tokens_used # Append messages that aren't already present, preserving order
for msg in accumulated_messages:
if msg.get('content') not in existing_contents:
messages.append(msg)
existing_contents.add(msg.get('content'))
# Ensure response has an agent attribute for state construction return messages
if not hasattr(response, 'agent'):
if all_agents and len(all_agents) > 0:
response.agent = all_agents[0] # Set default agent if missing
new_state = construct_state_from_response(response, all_agents)
return response.messages, response.tokens_used, new_state
async def run_turn(
messages, start_agent_name, agent_configs, tool_configs, start_turn_with_start_agent, state={}, additional_tool_configs=[], complete_request={}
):
"""
Coordinates a single 'turn' of conversation or processing among agents.
Includes validation, agent setup, optional greeting logic, error handling, and post-processing steps.
"""
logger.info("Running stateless turn")
print("Running stateless turn")
# Sort messages by the specified ordering
#messages = order_messages(messages)
# Merge any additional tool configs
tool_configs = tool_configs + additional_tool_configs
# Determine if this is a greeting turn
greeting_turn = not any(msg.get("role") != "system" for msg in messages)
turn_messages = []
# Initialize tokens_used as a dictionary
tokens_used = {"total": 0, "prompt": 0, "completion": 0}
agent_data = state.get("agent_data", [])
# If not a greeting turn, localize the last user or system messages
if not greeting_turn:
latest_assistant_msg = get_latest_assistant_msg(messages)
latest_non_assistant_msgs = get_latest_non_assistant_messages(messages)
msg_type = latest_non_assistant_msgs[-1]["role"]
# Determine the last agent from state/config
last_agent_name = get_last_agent_name(
state=state,
agent_configs=agent_configs,
start_agent_name=start_agent_name,
msg_type=msg_type,
latest_assistant_msg=latest_assistant_msg,
start_turn_with_start_agent=start_turn_with_start_agent
)
else:
# For a greeting turn, we assume the last agent is the start_agent_name
last_agent_name = start_agent_name
state["agent_data"] = agent_data
# Initialize all agents
logger.info("Initializing agents")
print("Initializing agents")
new_agents = get_agents(
agent_configs=agent_configs,
tool_configs=tool_configs,
complete_request=complete_request
)
# Prepare escalation agent
last_new_agent = get_agent_by_name(last_agent_name, new_agents)
# Gather external tools for Swarm
external_tools = get_external_tools(tool_configs)
logger.info(f"Found {len(external_tools)} external tools")
print(f"Found {len(external_tools)} external tools")
# If no validation error yet, proceed with the main run
logger.info("Running swarm run")
print("Running swarm run")
response = await swarm_run(
agent=last_new_agent,
messages=messages,
external_tools=external_tools,
tokens_used=tokens_used
)
logger.info("Swarm run completed")
print("Swarm run completed")
# Initialize response.messages if it doesn't exist
if not hasattr(response, 'messages'):
response.messages = []
# Convert the ResponseOutputMessage to a standard message format
if hasattr(response, 'new_items') and response.new_items and hasattr(response.new_items[-1], 'raw_item'):
raw_item = response.new_items[-1].raw_item
# Extract text content from ResponseOutputText objects
content = ""
if hasattr(raw_item, 'content') and raw_item.content:
for content_item in raw_item.content:
if hasattr(content_item, 'text'):
content += content_item.text
# Create a standard message dictionary
standard_message = {
"role": raw_item.role if hasattr(raw_item, 'role') else "assistant",
"content": content,
"sender": last_new_agent.name,
"created_at": None,
"response_type": "internal"
}
# Add the converted message to response messages
response.messages.append(standard_message)
logger.info("Converted message added to response messages")
print("Converted message added to response messages")
# Use a dictionary for tokens_used
tokens_used = {"total": 0, "prompt": 0, "completion": 0} # Default values as placeholders
# Ensure turn_messages can be extended with response.messages
if hasattr(response, 'messages') and isinstance(response.messages, list):
turn_messages.extend(response.messages)
logger.info(f"Completed run of agent: {last_new_agent.name}")
print(f"Completed run of agent: {last_new_agent.name}")
# Otherwise, duplicate the last response as external
logger.info("No post-processing agent found. Duplicating last response and setting to external.")
print("No post-processing agent found. Duplicating last response and setting to external.")
if turn_messages:
duplicate_msg = deepcopy(turn_messages[-1])
duplicate_msg["response_type"] = "external"
duplicate_msg["sender"] += " >> External"
# Ensure tokens_used remains a proper dictionary
if not isinstance(tokens_used, dict):
tokens_used = {"total": 0, "prompt": 0, "completion": 0} # Default values if not a dictionary
response = create_response(
messages=[duplicate_msg],
tokens_used=tokens_used,
agent=last_new_agent,
error_msg=''
)
# Ensure response has messages attribute
if hasattr(response, 'messages') and isinstance(response.messages, list):
turn_messages.extend(response.messages)
# Finalize the response
logger.info("Finalizing response")
print("Finalizing response")
return create_final_response(
response=response,
turn_messages=turn_messages,
tokens_used=tokens_used,
all_agents=new_agents
)
async def run_turn_streamed( async def run_turn_streamed(
messages, messages,
@ -252,18 +92,39 @@ async def run_turn_streamed(
prompt_configs, prompt_configs,
start_turn_with_start_agent, start_turn_with_start_agent,
state={}, state={},
additional_tool_configs=[], complete_request={},
complete_request={} enable_tracing=None
): ):
"""
Run a turn of the conversation with streaming responses.
A turn consists of all messages between user inputs and must follow these rules:
1. Each turn must have exactly one external message from an agent with external visibility
2. A turn can have multiple internal messages from internal agents
3. Each agent can output at most one regular message per parent
4. Control flows from parent to child, and child must return to parent after responding
5. Turn ends when an external agent outputs a message
"""
print("\n=== Starting new turn ===")
print(f"Starting agent: {start_agent_name}")
# Use enable_tracing from complete_request if available, otherwise default to False
enable_tracing = complete_request.get("enable_tracing", False) if enable_tracing is None else enable_tracing
messages = set_sys_message(messages) messages = set_sys_message(messages)
messages = add_sender_details_to_messages(messages)
is_greeting_turn = not any(msg.get("role") != "system" for msg in messages) is_greeting_turn = not any(msg.get("role") != "system" for msg in messages)
final_state = None # Initialize outside try block final_state = None
accumulated_messages = []
agent_message_counts = {} # Track messages per agent
child_call_counts = {} # Track parent->child calls
current_agent = None
parent_stack = []
try: try:
greeting_prompt = get_prompt_by_type(prompt_configs, PromptType.GREETING) # Handle greeting turn
if is_greeting_turn: if is_greeting_turn:
if not greeting_prompt: greeting_prompt = get_prompt_by_type(prompt_configs, PromptType.GREETING) or "How can I help you today?"
greeting_prompt = "How can I help you today?"
print("Greeting prompt not found. Using default: ", greeting_prompt)
message = { message = {
'content': greeting_prompt, 'content': greeting_prompt,
'role': 'assistant', 'role': 'assistant',
@ -271,21 +132,33 @@ async def run_turn_streamed(
'tool_calls': None, 'tool_calls': None,
'tool_call_id': None, 'tool_call_id': None,
'tool_name': None, 'tool_name': None,
'response_type': 'external' 'response_type': ResponseType.EXTERNAL.value
} }
print("Yielding greeting message: ", message) accumulated_messages.append(message)
print('-'*100)
print(f"Yielding message: {message}")
print('-'*100)
yield ('message', message) yield ('message', message)
final_state = { final_state = {
"last_agent_name": start_agent_name if start_agent_name else None, "last_agent_name": start_agent_name,
"tokens": {"total": 0, "prompt": 0, "completion": 0} "tokens": {"total": 0, "prompt": 0, "completion": 0},
"turn_messages": accumulated_messages
} }
print("Yielding done message") print('-'*100)
print(f"Yielding done: {final_state}")
print('-'*100)
yield ('done', {'state': final_state}) yield ('done', {'state': final_state})
return return
# Initialize agents and get external tools # Initialize agents and get external tools
new_agents = get_agents(agent_configs=agent_configs, tool_configs=tool_configs, complete_request=complete_request)
new_agents = get_agents(
agent_configs=agent_configs,
tool_configs=tool_configs,
complete_request=complete_request
)
new_agents = add_child_transfer_related_instructions_to_agents(new_agents)
new_agents = add_openai_recommended_instructions_to_agents(new_agents)
last_agent_name = get_last_agent_name( last_agent_name = get_last_agent_name(
state=state, state=state,
agent_configs=agent_configs, agent_configs=agent_configs,
@ -294,29 +167,35 @@ async def run_turn_streamed(
latest_assistant_msg=None, latest_assistant_msg=None,
start_turn_with_start_agent=start_turn_with_start_agent start_turn_with_start_agent=start_turn_with_start_agent
) )
last_new_agent = get_agent_by_name(last_agent_name, new_agents) current_agent = get_agent_by_name(last_agent_name, new_agents)
external_tools = get_external_tools(tool_configs) external_tools = get_external_tools(tool_configs)
current_agent = last_new_agent
tokens_used = {"total": 0, "prompt": 0, "completion": 0} tokens_used = {"total": 0, "prompt": 0, "completion": 0}
iter = 0
while True:
iter += 1
is_internal_agent = check_internal_visibility(current_agent)
print('-'*100)
print(f"Iteration {iter} of turn loop")
print(f"Current agent: {current_agent.name} (internal: {is_internal_agent})")
print(f"Parent stack: {[agent.name for agent in parent_stack]}")
print('-'*100)
messages = append_messages(messages, accumulated_messages)
# Run the current agent
stream_result = await swarm_run_streamed( stream_result = await swarm_run_streamed(
agent=last_new_agent, agent=current_agent,
messages=messages, messages=messages,
external_tools=external_tools, external_tools=external_tools,
tokens_used=tokens_used tokens_used=tokens_used,
enable_tracing=enable_tracing
) )
# Process streaming events
async for event in stream_result.stream_events(): async for event in stream_result.stream_events():
print('='*50) try:
print("Received event: ", event) # Handle web search events
print('-'*50)
# Handle raw response events and accumulate tokens
if event.type == "raw_response_event": if event.type == "raw_response_event":
if hasattr(event.data, 'type') and event.data.type == "response.completed" and event.data.response.usage: # Handle token usage counting
if hasattr(event.data.response, 'usage'): if hasattr(event.data, 'type') and event.data.type == "response.completed" and hasattr(event.data.response, 'usage'):
tokens_used["total"] += event.data.response.usage.total_tokens tokens_used["total"] += event.data.response.usage.total_tokens
tokens_used["prompt"] += event.data.response.usage.input_tokens tokens_used["prompt"] += event.data.response.usage.input_tokens
tokens_used["completion"] += event.data.response.usage.output_tokens tokens_used["completion"] += event.data.response.usage.output_tokens
@ -324,63 +203,35 @@ async def run_turn_streamed(
print(f"Found usage information. Updated cumulative tokens: {tokens_used}") print(f"Found usage information. Updated cumulative tokens: {tokens_used}")
print('-'*50) print('-'*50)
# Handle ResponseFunctionWebSearch specifically web_search_messages = handle_web_search_event(event, current_agent)
if hasattr(event, 'data') and hasattr(event.data, 'raw_item'): for message in web_search_messages:
raw_item = event.data.raw_item message['response_type'] = ResponseType.INTERNAL.value
print('-'*100)
# Check if it's a web search call print(f"Yielding message: {message}")
if (hasattr(raw_item, 'type') and raw_item.type == 'web_search_call') or ( print('-'*100)
isinstance(raw_item, dict) and raw_item.get('type') == 'web_search_call'
):
# Get call_id safely, regardless of structure
call_id = None
if hasattr(raw_item, 'id'):
call_id = raw_item.id
elif isinstance(raw_item, dict) and 'id' in raw_item:
call_id = raw_item['id']
else:
call_id = str(uuid.uuid4())
# Get status safely
status = 'unknown'
if hasattr(raw_item, 'status'):
status = raw_item.status
elif isinstance(raw_item, dict) and 'status' in raw_item:
status = raw_item['status']
# Emit a tool call for web search
message = {
'content': None,
'role': 'assistant',
'sender': current_agent.name if current_agent else None,
'tool_calls': [{
'function': {
'name': 'web_search',
'arguments': json.dumps({
'search_id': call_id,
'status': status
})
},
'id': call_id,
'type': 'function'
}],
'tool_call_id': None,
'tool_name': None,
'response_type': 'internal'
}
print("Yielding web search raw response message: ", message)
yield ('message', message) yield ('message', message)
if message.get('role') != 'tool':
message['content'] = f"Sender agent: {current_agent.name}\nContent: {message['content']}"
accumulated_messages.append(message)
continue continue
# Update current agent when it changes # Handle agent transfer
elif event.type == "agent_updated_stream_event": elif event.type == "agent_updated_stream_event":
# Skip self-transfers
if current_agent.name == event.new_agent.name: if current_agent.name == event.new_agent.name:
print(f"\nSkipping agent transfer attempt: {current_agent.name} -> {event.new_agent.name} (self-transfer)")
continue continue
tool_call_id = str(uuid.uuid4()) # Check if we've already called this child agent too many times
parent_child_key = f"{current_agent.name}:{event.new_agent.name}"
current_count = child_call_counts.get(parent_child_key, 0)
if current_count >= event.new_agent.max_calls_per_parent_agent:
print(f"Skipping transfer from {current_agent.name} to {event.new_agent.name} (max calls reached from parent to child)")
continue
# yield the transfer invocation # Transfer to new agent
tool_call_id = str(uuid.uuid4())
message = { message = {
'content': None, 'content': None,
'role': 'assistant', 'role': 'assistant',
@ -397,12 +248,14 @@ async def run_turn_streamed(
}], }],
'tool_call_id': None, 'tool_call_id': None,
'tool_name': None, 'tool_name': None,
'response_type': 'internal' 'response_type': ResponseType.INTERNAL.value
} }
print("Yielding message: ", message) print('-'*100)
print(f"Yielding message: {message}")
print('-'*100)
yield ('message', message) yield ('message', message)
# yield the transfer result # Record transfer result
message = { message = {
'content': json.dumps({ 'content': json.dumps({
'assistant': event.new_agent.name 'assistant': event.new_agent.name
@ -411,60 +264,41 @@ async def run_turn_streamed(
'sender': None, 'sender': None,
'tool_calls': None, 'tool_calls': None,
'tool_call_id': tool_call_id, 'tool_call_id': tool_call_id,
'tool_name': 'transfer_to_agent', 'tool_name': 'transfer_to_agent'
} }
print("Yielding message: ", message) print('-'*100)
print(f"Yielding message: {message}")
print('-'*100)
yield ('message', message) yield ('message', message)
# Update tracking and switch to child
if check_internal_visibility(event.new_agent):
child_call_counts[parent_child_key] = current_count + 1
parent_stack.append(current_agent)
current_agent = event.new_agent current_agent = event.new_agent
# Handle regular messages and tool calls
elif event.type == "run_item_stream_event":
if event.item.type == "tool_call_item":
# Check if it's a web search call
if hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_call':
web_search_messages = handle_web_search_event(event, current_agent)
for message in web_search_messages:
message['response_type'] = ResponseType.INTERNAL.value
print('-'*100)
print(f"Yielding message: {message}")
print('-'*100)
yield ('message', message)
if message.get('role') != 'tool':
message['content'] = f"Sender agent: {current_agent.name}\nContent: {message['content']}"
accumulated_messages.append(message)
continue continue
# Handle run items (tools, messages, etc) # Handle regular tool calls
elif event.type == "run_item_stream_event":
current_agent = event.item.agent
if event.item.type == "tool_call_item":
# Check if it's a ResponseFunctionWebSearch object
if hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_call':
call_id = event.item.raw_item.id if hasattr(event.item.raw_item, 'id') else str(uuid.uuid4())
message = { message = {
'content': None, 'content': None,
'role': 'assistant', 'role': 'assistant',
'sender': current_agent.name if current_agent else None, 'sender': current_agent.name,
'tool_calls': [{
'function': {
'name': 'web_search',
'arguments': json.dumps({
'search_id': call_id
})
},
'id': call_id,
'type': 'function'
}],
'tool_call_id': None,
'tool_name': None,
'response_type': 'internal'
}
print("Yielding message: ", message)
yield ('message', message)
result_message = {
'content': "Web search done",
'role': 'tool',
'sender': None,
'tool_calls': None,
'tool_call_id': call_id,
'tool_name': 'web_search',
'response_type': 'internal'
}
print("Yielding web search results: ", result_message)
yield ('message', result_message)
else:
# Handle normal tool calls
message = {
'content': None,
'role': 'assistant',
'sender': current_agent.name if current_agent else None,
'tool_calls': [{ 'tool_calls': [{
'function': { 'function': {
'name': event.item.raw_item.name, 'name': event.item.raw_item.name,
@ -475,64 +309,64 @@ async def run_turn_streamed(
}], }],
'tool_call_id': None, 'tool_call_id': None,
'tool_name': None, 'tool_name': None,
'response_type': 'internal' 'response_type': ResponseType.INTERNAL.value
} }
print("Yielding message: ", message) print('-'*100)
print(f"Yielding message: {message}")
print('-'*100)
yield ('message', message) yield ('message', message)
message['content'] = f"Sender agent: {current_agent.name}\nContent: {message['content']}"
accumulated_messages.append(message)
elif event.item.type == "tool_call_output_item": elif event.item.type == "tool_call_output_item":
# Check if it's a web search result # Get the tool name and call id from raw_item
if isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results': tool_call_id = None
call_id = event.item.raw_item.get('search_id', event.item.raw_item.get('id', str(uuid.uuid4())))
message = {
'content': str(event.item.output),
'role': 'tool',
'sender': None,
'tool_calls': None,
'tool_call_id': call_id,
'tool_name': 'web_search',
'response_type': 'internal'
}
else:
# Safe extraction of call_id and name
call_id = None
tool_name = None tool_name = None
# Handle different types of raw_item # Try to get call_id from various possible locations
if isinstance(event.item.raw_item, dict): if hasattr(event.item.raw_item, 'call_id'):
call_id = event.item.raw_item.get('call_id') tool_call_id = event.item.raw_item.call_id
tool_name = event.item.raw_item.get('name') elif isinstance(event.item.raw_item, dict) and 'call_id' in event.item.raw_item:
elif hasattr(event.item.raw_item, 'call_id'): tool_call_id = event.item.raw_item['call_id']
call_id = event.item.raw_item.call_id
# Try to get tool name from various possible locations
if hasattr(event.item.raw_item, 'name'): if hasattr(event.item.raw_item, 'name'):
tool_name = event.item.raw_item.name tool_name = event.item.raw_item.name
elif isinstance(event.item.raw_item, dict):
if 'name' in event.item.raw_item:
tool_name = event.item.raw_item['name']
elif 'type' in event.item.raw_item and event.item.raw_item['type'] == 'function_call_output':
# For function call outputs, try to infer from context
tool_name = 'recommendation' # Default for function calls
# Fallback to event item if available
if not tool_name and hasattr(event.item, 'tool_name'):
tool_name = event.item.tool_name
if not tool_call_id and hasattr(event.item, 'tool_call_id'):
tool_call_id = event.item.tool_call_id
message = { message = {
'content': str(event.item.output), 'content': str(event.item.output),
'role': 'tool', 'role': 'tool',
'sender': None, 'sender': None,
'tool_calls': None, 'tool_calls': None,
'tool_call_id': call_id, 'tool_call_id': tool_call_id,
'tool_name': tool_name, 'tool_name': tool_name,
'response_type': 'internal' 'response_type': ResponseType.INTERNAL.value
} }
print('-'*100)
print("Yielding message: ", message) print(f"Yielding tool call output message: {message}")
print('-'*100)
yield ('message', message) yield ('message', message)
elif event.item.type == "message_output_item": elif event.item.type == "message_output_item":
# Extract content and citations
content = "" content = ""
url_citations = [] url_citations = []
# Extract text content and any URL citations
if hasattr(event.item.raw_item, 'content'): if hasattr(event.item.raw_item, 'content'):
for content_item in event.item.raw_item.content: for content_item in event.item.raw_item.content:
# Handle text content
if hasattr(content_item, 'text'): if hasattr(content_item, 'text'):
content += content_item.text content += content_item.text
# Extract URL citations if present
if hasattr(content_item, 'annotations'): if hasattr(content_item, 'annotations'):
for annotation in content_item.annotations: for annotation in content_item.annotations:
if hasattr(annotation, 'type') and annotation.type == 'url_citation': if hasattr(annotation, 'type') and annotation.type == 'url_citation':
@ -544,7 +378,10 @@ async def run_turn_streamed(
} }
url_citations.append(citation) url_citations.append(citation)
# Create message with URL citations if they exist # Determine message type and create message
is_internal = check_internal_visibility(current_agent)
response_type = ResponseType.INTERNAL.value if is_internal else ResponseType.EXTERNAL.value
message = { message = {
'content': content, 'content': content,
'role': 'assistant', 'role': 'assistant',
@ -552,110 +389,97 @@ async def run_turn_streamed(
'tool_calls': None, 'tool_calls': None,
'tool_call_id': None, 'tool_call_id': None,
'tool_name': None, 'tool_name': None,
'response_type': 'external' 'response_type': response_type
} }
# Add citations if any were found
if url_citations: if url_citations:
message['citations'] = url_citations message['citations'] = url_citations
print("Yielding message: ", message) # Track that this agent has responded
if not message.get('tool_calls'): # If there are no tool calls, it's a content response
agent_message_counts[current_agent.name] = 1
print('-'*100)
print(f"Yielding message: {message}")
print('-'*100)
yield ('message', message) yield ('message', message)
message['content'] = f"Sender agent: {current_agent.name}\nContent: {message['content']}"
# Handle web search function call events accumulated_messages.append(message)
elif event.item.type == "web_search_call_item" or (hasattr(event.item, 'raw_item') and hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_call'): # Return to parent or end turn
# Extract web search call ID if available if is_internal and parent_stack:
call_id = None # Create tool call for control transition
if hasattr(event.item.raw_item, 'id'): tool_call_id = str(uuid.uuid4())
call_id = event.item.raw_item.id transition_message = {
message = {
'content': None, 'content': None,
'role': 'assistant', 'role': 'assistant',
'sender': current_agent.name if current_agent else None, 'sender': current_agent.name,
'tool_calls': [{ 'tool_calls': [{
'function': { 'function': {
'name': 'web_search', 'name': 'transfer_to_agent',
'arguments': json.dumps({ 'arguments': json.dumps({
'search_id': call_id 'assistant': parent_stack[-1].name
}) })
}, },
'id': call_id or str(uuid.uuid4()), 'id': tool_call_id,
'type': 'function' 'type': 'function'
}], }],
'tool_call_id': None, 'tool_call_id': None,
'tool_name': None, 'tool_name': None,
'response_type': 'internal' 'response_type': ResponseType.INTERNAL.value
} }
print("Yielding web search message: ", message) print('-'*100)
yield ('message', message) print(f"Yielding control transition message: {transition_message}")
print('-'*100)
yield ('message', transition_message)
# Handle web search results # Create tool response for control transition
elif event.item.type == "web_search_results_item" or ( transition_response = {
hasattr(event.item, 'raw_item') and ( 'content': json.dumps({
(hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_results') or 'assistant': parent_stack[-1].name
(isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results') }),
)
):
# Extract call_id safely
call_id = None
raw_item = event.item.raw_item
# Try several ways to get the search_id or id
if hasattr(raw_item, 'search_id'):
call_id = raw_item.search_id
elif isinstance(raw_item, dict) and 'search_id' in raw_item:
call_id = raw_item['search_id']
elif hasattr(raw_item, 'id'):
call_id = raw_item.id
elif isinstance(raw_item, dict) and 'id' in raw_item:
call_id = raw_item['id']
else:
call_id = str(uuid.uuid4())
# Extract results content safely
results = {}
# Try event.item.output first
if hasattr(event.item, 'output'):
results = event.item.output
# Then try raw_item.results
elif hasattr(raw_item, 'results'):
results = raw_item.results
elif isinstance(raw_item, dict) and 'results' in raw_item:
results = raw_item['results']
# Format the results for output
results_str = ""
try:
results_str = json.dumps(results) if results else ""
except Exception as e:
print(f"Error serializing results: {str(e)}")
results_str = str(results)
message = {
'content': results_str,
'role': 'tool', 'role': 'tool',
'sender': None, 'sender': None,
'tool_calls': None, 'tool_calls': None,
'tool_call_id': call_id, 'tool_call_id': tool_call_id,
'tool_name': 'web_search', 'tool_name': 'transfer_to_agent'
'response_type': 'internal'
} }
print("Yielding web search results: ", message) print('-'*100)
yield ('message', message) print(f"Yielding control transition response: {transition_response}")
print('-'*100)
yield ('message', transition_response)
print(f"\n{'='*50}\n") current_agent = parent_stack.pop()
continue
elif not is_internal:
break
# After all events are processed, set final state except Exception as e:
print("\n=== Error in stream event processing ===")
print(f"Error: {str(e)}")
print("Event details:")
print(f"Event type: {event.type if hasattr(event, 'type') else 'unknown'}")
if hasattr(event, '__dict__'):
print(f"Event attributes: {event.__dict__}")
print(f"Full event object: {event}")
print(f"Traceback: {traceback.format_exc()}")
print("=" * 50)
raise
# Break main loop if we've output an external message
if not is_internal_agent and current_agent.name in agent_message_counts:
break
# Set final state
final_state = { final_state = {
"last_agent_name": current_agent.name if current_agent else None, "last_agent_name": current_agent.name if current_agent else None,
"tokens": tokens_used "tokens": tokens_used,
"turn_messages": accumulated_messages
} }
print('-'*100)
print(f"Yielding done: {final_state}")
print('-'*100)
yield ('done', {'state': final_state}) yield ('done', {'state': final_state})
except Exception as e: except Exception as e:
print(traceback.format_exc()) print(traceback.format_exc())
print(f"Error in stream processing: {str(e)}") print(f"Error in stream processing: {str(e)}")
print("Yielding error event:", {'error': str(e), 'state': final_state}) yield ('error', {'error': str(e), 'state': final_state})
yield ('error', {'error': str(e), 'state': final_state}) # Include final_state in error response

View file

@ -3,7 +3,7 @@ import json
import aiohttp import aiohttp
import jwt import jwt
import hashlib import hashlib
from agents import OpenAIChatCompletionsModel from agents import OpenAIChatCompletionsModel, trace, add_trace_processor
# Import helper functions needed for get_agents # Import helper functions needed for get_agents
from .helpers.access import ( from .helpers.access import (
@ -13,12 +13,12 @@ from .helpers.access import (
from .helpers.instructions import ( from .helpers.instructions import (
add_rag_instructions_to_agent add_rag_instructions_to_agent
) )
from .types import outputVisibility
from agents import Agent as NewAgent, Runner, FunctionTool, RunContextWrapper, ModelSettings, WebSearchTool from agents import Agent as NewAgent, Runner, FunctionTool, RunContextWrapper, ModelSettings, WebSearchTool
from .tracing import AgentTurnTraceProcessor
# Add import for OpenAI functionality # Add import for OpenAI functionality
from src.utils.common import common_logger as logger, generate_openai_output from src.utils.common import generate_openai_output
from typing import Any from typing import Any
from dataclasses import asdict
import asyncio import asyncio
from mcp import ClientSession from mcp import ClientSession
from mcp.client.sse import sse_client from mcp.client.sse import sse_client
@ -54,7 +54,7 @@ async def mock_tool(tool_name: str, args: str, description: str, mock_instructio
response_content = generate_openai_output(messages, output_type='text', model=PROVIDER_DEFAULT_MODEL) response_content = generate_openai_output(messages, output_type='text', model=PROVIDER_DEFAULT_MODEL)
return response_content return response_content
except Exception as e: except Exception as e:
logger.error(f"Error in mock_tool: {str(e)}") print(f"Error in mock_tool: {str(e)}")
return f"Error: {str(e)}" return f"Error: {str(e)}"
async def call_webhook(tool_name: str, args: str, webhook_url: str, signing_secret: str) -> str: async def call_webhook(tool_name: str, args: str, webhook_url: str, signing_secret: str) -> str:
@ -91,7 +91,7 @@ async def call_webhook(tool_name: str, args: str, webhook_url: str, signing_secr
print(f"Webhook error: {error_msg}") print(f"Webhook error: {error_msg}")
return f"Error: {error_msg}" return f"Error: {error_msg}"
except Exception as e: except Exception as e:
logger.error(f"Exception in call_webhook: {str(e)}") print(f"Exception in call_webhook: {str(e)}")
return f"Error: Failed to call webhook - {str(e)}" return f"Error: Failed to call webhook - {str(e)}"
async def call_mcp(tool_name: str, args: str, mcp_server_url: str) -> str: async def call_mcp(tool_name: str, args: str, mcp_server_url: str) -> str:
@ -106,7 +106,7 @@ async def call_mcp(tool_name: str, args: str, mcp_server_url: str) -> str:
return json_output return json_output
except Exception as e: except Exception as e:
logger.error(f"Error in call_mcp: {str(e)}") print(f"Error in call_mcp: {str(e)}")
return f"Error: {str(e)}" return f"Error: {str(e)}"
async def catch_all(ctx: RunContextWrapper[Any], args: str, tool_name: str, tool_config: dict, complete_request: dict) -> str: async def catch_all(ctx: RunContextWrapper[Any], args: str, tool_name: str, tool_config: dict, complete_request: dict) -> str:
@ -143,7 +143,7 @@ async def catch_all(ctx: RunContextWrapper[Any], args: str, tool_name: str, tool
response_content = await call_webhook(tool_name, args, webhook_url, signing_secret) response_content = await call_webhook(tool_name, args, webhook_url, signing_secret)
return response_content return response_content
except Exception as e: except Exception as e:
logger.error(f"Error in catch_all: {str(e)}") print(f"Error in catch_all: {str(e)}")
return f"Error: {str(e)}" return f"Error: {str(e)}"
@ -177,6 +177,8 @@ def get_rag_tool(config: dict, complete_request: dict) -> FunctionTool:
else: else:
return None return None
DEFAULT_MAX_CALLS_PER_PARENT_AGENT = 3
def get_agents(agent_configs, tool_configs, complete_request): def get_agents(agent_configs, tool_configs, complete_request):
""" """
Creates and initializes Agent objects based on their configurations and connections. Creates and initializes Agent objects based on their configurations and connections.
@ -191,7 +193,6 @@ def get_agents(agent_configs, tool_configs, complete_request):
new_agent_name_to_index = {} new_agent_name_to_index = {}
# Create Agent objects from config # Create Agent objects from config
for agent_config in agent_configs: for agent_config in agent_configs:
logger.debug(f"Processing config for agent: {agent_config['name']}")
print("="*100) print("="*100)
print(f"Processing config for agent: {agent_config['name']}") print(f"Processing config for agent: {agent_config['name']}")
@ -204,14 +205,12 @@ def get_agents(agent_configs, tool_configs, complete_request):
# Prepare tool lists for this agent # Prepare tool lists for this agent
external_tools = [] external_tools = []
logger.debug(f"Agent {agent_config['name']} has {len(agent_config['tools'])} configured tools")
print(f"Agent {agent_config['name']} has {len(agent_config['tools'])} configured tools") print(f"Agent {agent_config['name']} has {len(agent_config['tools'])} configured tools")
new_tools = [] new_tools = []
rag_tool = get_rag_tool(agent_config, complete_request) rag_tool = get_rag_tool(agent_config, complete_request)
if rag_tool: if rag_tool:
new_tools.append(rag_tool) new_tools.append(rag_tool)
logger.debug(f"Added rag tool to agent {agent_config['name']}")
print(f"Added rag tool to agent {agent_config['name']}") print(f"Added rag tool to agent {agent_config['name']}")
for tool_name in agent_config["tools"]: for tool_name in agent_config["tools"]:
@ -235,22 +234,22 @@ def get_agents(agent_configs, tool_configs, complete_request):
catch_all(ctx, args, _tool_name, _tool_config, _complete_request) catch_all(ctx, args, _tool_name, _tool_config, _complete_request)
) )
new_tools.append(tool) new_tools.append(tool)
logger.debug(f"Added tool {tool_name} to agent {agent_config['name']}")
print(f"Added tool {tool_name} to agent {agent_config['name']}") print(f"Added tool {tool_name} to agent {agent_config['name']}")
else: else:
logger.warning(f"Tool {tool_name} not found in tool_configs")
print(f"WARNING: Tool {tool_name} not found in tool_configs") print(f"WARNING: Tool {tool_name} not found in tool_configs")
# Create the agent object # Create the agent object
logger.debug(f"Creating Agent object for {agent_config['name']}")
print(f"Creating Agent object for {agent_config['name']}") print(f"Creating Agent object for {agent_config['name']}")
# add the name and description to the agent instructions # add the name and description to the agent instructions
agent_instructions = f"## Your Name\n{agent_config['name']}\n\n## Description\n{agent_config['description']}\n\n## Instructions\n{agent_config['instructions']}" agent_instructions = f"## Your Name\n{agent_config['name']}\n\n## Description\n{agent_config['description']}\n\n## Instructions\n{agent_config['instructions']}"
try: try:
# Identify the model
model_name = agent_config["model"] if agent_config["model"] else PROVIDER_DEFAULT_MODEL model_name = agent_config["model"] if agent_config["model"] else PROVIDER_DEFAULT_MODEL
print(f"Using model: {model_name}") print(f"Using model: {model_name}")
model=OpenAIChatCompletionsModel(model=model_name, openai_client=client) if client else agent_config["model"] model=OpenAIChatCompletionsModel(model=model_name, openai_client=client) if client else agent_config["model"]
# Create the agent object
new_agent = NewAgent( new_agent = NewAgent(
name=agent_config["name"], name=agent_config["name"],
instructions=agent_instructions, instructions=agent_instructions,
@ -260,13 +259,26 @@ def get_agents(agent_configs, tool_configs, complete_request):
model_settings=ModelSettings(temperature=0.0) model_settings=ModelSettings(temperature=0.0)
) )
# Set the max calls per parent agent
new_agent.max_calls_per_parent_agent = agent_config.get("maxCallsPerParentAgent", DEFAULT_MAX_CALLS_PER_PARENT_AGENT)
if not agent_config.get("maxCallsPerParentAgent", None):
print(f"WARNING: Max calls per parent agent not received for agent {new_agent.name}. Using rowboat_agents default of {DEFAULT_MAX_CALLS_PER_PARENT_AGENT}")
else:
print(f"Max calls per parent agent for agent {new_agent.name}: {new_agent.max_calls_per_parent_agent}")
# Set output visibility
new_agent.output_visibility = agent_config.get("outputVisibility", outputVisibility.EXTERNAL.value)
if not agent_config.get("outputVisibility", None):
print(f"WARNING: Output visibility not received for agent {new_agent.name}. Using rowboat_agents default of {new_agent.output_visibility}")
else:
print(f"Output visibility for agent {new_agent.name}: {new_agent.output_visibility}")
# Handle the connected agents
new_agent_to_children[agent_config["name"]] = agent_config.get("connectedAgents", []) new_agent_to_children[agent_config["name"]] = agent_config.get("connectedAgents", [])
new_agent_name_to_index[agent_config["name"]] = len(new_agents) new_agent_name_to_index[agent_config["name"]] = len(new_agents)
new_agents.append(new_agent) new_agents.append(new_agent)
logger.debug(f"Successfully created agent: {agent_config['name']}")
print(f"Successfully created agent: {agent_config['name']}") print(f"Successfully created agent: {agent_config['name']}")
except Exception as e: except Exception as e:
logger.error(f"Failed to create agent {agent_config['name']}: {str(e)}")
print(f"ERROR: Failed to create agent {agent_config['name']}: {str(e)}") print(f"ERROR: Failed to create agent {agent_config['name']}: {str(e)}")
raise raise
@ -281,91 +293,20 @@ def get_agents(agent_configs, tool_configs, complete_request):
print("="*100) print("="*100)
return new_agents return new_agents
# Initialize a flag to track if the trace processor is added
def create_response(messages=None, tokens_used=None, agent=None, error_msg=''): trace_processor_added = False
"""
Create a Response object with the given parameters.
Args:
messages: List of messages
tokens_used: Dictionary tracking token usage
agent: The agent that generated the response
error_msg: Error message if any
Returns:
Response object
"""
if messages is None:
messages = []
if tokens_used is None:
tokens_used = {}
return NewResponse(
messages=messages,
agent=agent,
tokens_used=tokens_used,
error_msg=error_msg
)
async def run(
agent,
messages,
external_tools=None,
tokens_used=None
):
"""
Wrapper function for initializing and running the Swarm client.
"""
logger.info(f"Initializing Swarm client for agent: {agent.name}")
print(f"Initializing Swarm client for agent: {agent.name}")
# Initialize default parameters
if external_tools is None:
external_tools = []
if tokens_used is None:
tokens_used = {}
# Format messages to ensure they're compatible with the OpenAI API
formatted_messages = []
for msg in messages:
if isinstance(msg, dict) and "content" in msg:
formatted_msg = {
"role": msg.get("role", "user"),
"content": msg["content"]
}
formatted_messages.append(formatted_msg)
else:
formatted_messages.append({
"role": "user",
"content": str(msg)
})
logger.info("Beginning Swarm run")
print("Beginning Swarm run")
try:
response = await Runner.run(agent, formatted_messages)
except Exception as e:
logger.error(f"Error during run: {str(e)}")
print(f"Error during run: {str(e)}")
raise
logger.info(f"Completed Swarm run for agent: {agent.name}")
print(f"Completed Swarm run for agent: {agent.name}")
return response
async def run_streamed( async def run_streamed(
agent, agent,
messages, messages,
external_tools=None, external_tools=None,
tokens_used=None tokens_used=None,
enable_tracing=False
): ):
""" """
Wrapper function for initializing and running the Swarm client in streaming mode. Wrapper function for initializing and running the Swarm client in streaming mode.
""" """
logger.info(f"Initializing Swarm streaming client for agent: {agent.name}") print(f"Initializing streaming client for agent: {agent.name}")
print(f"Initializing Swarm streaming client for agent: {agent.name}")
# Initialize default parameters # Initialize default parameters
if external_tools is None: if external_tools is None:
@ -388,14 +329,39 @@ async def run_streamed(
"content": str(msg) "content": str(msg)
}) })
logger.info("Beginning Swarm streaming run") print("Beginning streaming run")
print("Beginning Swarm streaming run")
try: try:
# Use the Runner.run_streamed method # Add our custom trace processor only if tracing is enabled
global trace_processor_added
if enable_tracing and not trace_processor_added:
trace_processor = AgentTurnTraceProcessor()
add_trace_processor(trace_processor)
trace_processor_added = True
# Get the stream result without trace context first
stream_result = Runner.run_streamed(agent, formatted_messages) stream_result = Runner.run_streamed(agent, formatted_messages)
# If tracing is enabled, wrap the stream_events to handle tracing
if enable_tracing:
original_stream_events = stream_result.stream_events
async def wrapped_stream_events():
# Create trace context inside the async function
with trace(f"Agent turn: {agent.name}") as trace_ctx:
try:
async for event in original_stream_events():
yield event
except GeneratorExit:
# Handle generator exit gracefully
raise
except Exception as e:
print(f"Error in stream events: {str(e)}")
raise
stream_result.stream_events = wrapped_stream_events
return stream_result return stream_result
except Exception as e: except Exception as e:
logger.error(f"Error during streaming run: {str(e)}")
print(f"Error during streaming run: {str(e)}") print(f"Error during streaming run: {str(e)}")
raise raise

View file

@ -3,7 +3,7 @@ from src.utils.common import generate_llm_output
import os import os
import copy import copy
from .swarm_wrapper import Agent, Response, create_response from .execute_turn import Agent, Response, create_response
from src.utils.common import common_logger, generate_openai_output, update_tokens_used from src.utils.common import common_logger, generate_openai_output, update_tokens_used
logger = common_logger logger = common_logger

View file

@ -1,7 +1,5 @@
from .access import get_agent_config_by_name, get_agent_data_by_name from .access import get_agent_config_by_name, get_agent_data_by_name
from src.graph.types import ControlType from src.graph.types import ControlType
from src.utils.common import common_logger
logger = common_logger
def get_last_agent_name(state, agent_configs, start_agent_name, msg_type, latest_assistant_msg, start_turn_with_start_agent): def get_last_agent_name(state, agent_configs, start_agent_name, msg_type, latest_assistant_msg, start_turn_with_start_agent):
default_last_agent_name = state.get("last_agent_name", '') default_last_agent_name = state.get("last_agent_name", '')
@ -9,7 +7,7 @@ def get_last_agent_name(state, agent_configs, start_agent_name, msg_type, latest
specific_agent_data = get_agent_data_by_name(default_last_agent_name, state.get("agent_data", [])) specific_agent_data = get_agent_data_by_name(default_last_agent_name, state.get("agent_data", []))
# Overrides for special cases # Overrides for special cases
logger.info("Setting agent control based on last agent and control type") print("Setting agent control based on last agent and control type")
if msg_type == "tool": if msg_type == "tool":
last_agent_name = default_last_agent_name last_agent_name = default_last_agent_name
assert last_agent_name == latest_assistant_msg.get("sender", ''), "Last agent name does not match sender of latest assistant message during tool call handling" assert last_agent_name == latest_assistant_msg.get("sender", ''), "Last agent name does not match sender of latest assistant message during tool call handling"
@ -22,7 +20,7 @@ def get_last_agent_name(state, agent_configs, start_agent_name, msg_type, latest
if control_type == ControlType.PARENT_AGENT.value: if control_type == ControlType.PARENT_AGENT.value:
last_agent_name = specific_agent_data.get("most_recent_parent_name", None) if specific_agent_data else None last_agent_name = specific_agent_data.get("most_recent_parent_name", None) if specific_agent_data else None
if not last_agent_name: if not last_agent_name:
logger.error("Most recent parent is empty, defaulting to same agent instead") print("Most recent parent is empty, defaulting to same agent instead")
last_agent_name = default_last_agent_name last_agent_name = default_last_agent_name
elif control_type == ControlType.START_AGENT.value: elif control_type == ControlType.START_AGENT.value:
last_agent_name = start_agent_name last_agent_name = start_agent_name
@ -30,7 +28,7 @@ def get_last_agent_name(state, agent_configs, start_agent_name, msg_type, latest
last_agent_name = default_last_agent_name last_agent_name = default_last_agent_name
if default_last_agent_name != last_agent_name: if default_last_agent_name != last_agent_name:
logger.info(f"Last agent name changed from {default_last_agent_name} to {last_agent_name} due to control settings") print(f"Last agent name changed from {default_last_agent_name} to {last_agent_name} due to control settings")
return last_agent_name return last_agent_name

View file

@ -1,4 +1,4 @@
from src.graph.instructions import TRANSFER_CHILDREN_INSTRUCTIONS, TRANSFER_PARENT_AWARE_INSTRUCTIONS, RAG_INSTRUCTIONS, ERROR_ESCALATION_AGENT_INSTRUCTIONS, TRANSFER_GIVE_UP_CONTROL_INSTRUCTIONS, SYSTEM_MESSAGE from src.graph.instructions import TRANSFER_CHILDREN_INSTRUCTIONS, TRANSFER_PARENT_AWARE_INSTRUCTIONS, RAG_INSTRUCTIONS, ERROR_ESCALATION_AGENT_INSTRUCTIONS, TRANSFER_GIVE_UP_CONTROL_INSTRUCTIONS, SYSTEM_MESSAGE, CHILD_TRANSFER_RELATED_INSTRUCTIONS
def add_transfer_instructions_to_parent_agents(agent, children, transfer_functions): def add_transfer_instructions_to_parent_agents(agent, children, transfer_functions):
other_agent_name_descriptions_tools = f'\n{'-'*100}\n'.join([f"Name: {agent.name}\nDescription: {agent.description if agent.description else ''}\nTool for transfer: {transfer_functions[agent.name].__name__}" for agent in children.values()]) other_agent_name_descriptions_tools = f'\n{'-'*100}\n'.join([f"Name: {agent.name}\nDescription: {agent.description if agent.description else ''}\nTool for transfer: {transfer_functions[agent.name].__name__}" for agent in children.values()])
@ -37,3 +37,8 @@ def get_universal_system_message(messages):
def add_universal_system_message_to_agent(agent, universal_sys_msg): def add_universal_system_message_to_agent(agent, universal_sys_msg):
agent.instructions = agent.instructions + f'\n\n{'-'*100}\n\n' + universal_sys_msg agent.instructions = agent.instructions + f'\n\n{'-'*100}\n\n' + universal_sys_msg
return agent return agent
def add_child_transfer_related_instructions(agent):
prompt = CHILD_TRANSFER_RELATED_INSTRUCTIONS
agent.instructions = agent.instructions + f'\n\n{'-'*100}\n\n' + prompt
return agent

View file

@ -0,0 +1,268 @@
import json
import uuid
import traceback
def handle_web_search_event(event, current_agent):
"""
Helper function to handle all web search related events.
Returns a list of messages to yield.
"""
messages = []
try:
# Handle raw response web search
if event.type == "raw_response_event":
if hasattr(event, 'data') and hasattr(event.data, 'raw_item'):
raw_item = event.data.raw_item
if (hasattr(raw_item, 'type') and raw_item.type == 'web_search_call') or (
isinstance(raw_item, dict) and raw_item.get('type') == 'web_search_call'
):
call_id = None
if hasattr(raw_item, 'id'):
call_id = raw_item.id
elif isinstance(raw_item, dict) and 'id' in raw_item:
call_id = raw_item['id']
else:
call_id = str(uuid.uuid4())
status = 'unknown'
if hasattr(raw_item, 'status'):
status = raw_item.status
elif isinstance(raw_item, dict) and 'status' in raw_item:
status = raw_item['status']
tool_call_msg = {
'content': None,
'role': 'assistant',
'sender': current_agent.name if current_agent else None,
'tool_calls': [{
'function': {
'name': 'web_search',
'arguments': json.dumps({
'search_id': call_id,
'status': status
})
},
'id': call_id,
'type': 'function'
}],
'tool_call_id': None,
'tool_name': None,
'response_type': 'internal'
}
print(f"Condition for tool call matched in raw_response_event. Appending tool call message: {tool_call_msg}")
messages.append(tool_call_msg)
tool_call_output_dummy_msg = {
'content': 'Web search completed.',
'role': 'tool',
'sender': None,
'tool_calls': None,
'tool_call_id': call_id,
'tool_name': 'web_search',
'response_type': 'internal'
}
messages.append(tool_call_output_dummy_msg)
# Handle run item web search events
elif event.type == "run_item_stream_event":
if event.item.type == "tool_call_item":
try:
# Check if it's a web search call
if hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_call':
call_id = event.item.raw_item.id if hasattr(event.item.raw_item, 'id') else str(uuid.uuid4())
tool_call_msg = {
'content': None,
'role': 'assistant',
'sender': current_agent.name if current_agent else None,
'tool_calls': [{
'function': {
'name': 'web_search',
'arguments': json.dumps({
'search_id': call_id
})
},
'id': call_id,
'type': 'function'
}],
'tool_call_id': None,
'tool_name': None,
'response_type': 'internal'
}
print(f"Condition for tool call matched in run_item_stream_event. Appending tool call message: {tool_call_msg}")
messages.append(tool_call_msg)
tool_call_output_dummy_msg = {
'content': 'Web search completed.',
'role': 'tool',
'sender': None,
'tool_calls': None,
'tool_call_id': call_id,
'tool_name': 'web_search',
'response_type': 'internal'
}
messages.append(tool_call_output_dummy_msg)
else:
# Handle regular tool calls
tool_call_msg = {
'content': None,
'role': 'assistant',
'sender': current_agent.name if current_agent else None,
'tool_calls': [{
'function': {
'name': event.item.raw_item.name,
'arguments': event.item.raw_item.arguments
},
'id': event.item.raw_item.call_id,
'type': 'function'
}],
'tool_call_id': None,
'tool_name': None,
'response_type': 'internal'
}
print(f"Condition for tool call matched in run_item_stream_event. Appending tool call message: {tool_call_msg}")
messages.append(tool_call_msg)
tool_call_output_dummy_msg = {
'content': 'Web search completed.',
'role': 'tool',
'sender': None,
'tool_calls': None,
'tool_call_id': call_id,
'tool_name': 'web_search',
'response_type': 'internal'
}
messages.append(tool_call_output_dummy_msg)
except Exception as e:
print("\n=== Error in tool_call_item handling ===")
print(f"Error: {str(e)}")
print(f"Event type: {event.type}")
print(f"Event item type: {event.item.type}")
print("Event details:")
print(f"Raw item: {event.item.raw_item}")
if hasattr(event.item.raw_item, '__dict__'):
print(f"Raw item attributes: {event.item.raw_item.__dict__}")
print(f"Traceback: {traceback.format_exc()}")
print("=" * 50)
raise
elif event.item.type == "tool_call_output_item":
if isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results':
call_id = event.item.raw_item.get('search_id', event.item.raw_item.get('id', str(uuid.uuid4())))
tool_call_output_msg = {
'content': str(event.item.output),
'role': 'tool',
'sender': None,
'tool_calls': None,
'tool_call_id': call_id,
'tool_name': 'web_search',
'response_type': 'internal'
}
print(f"Condition for tool call output matched in run_item_stream_event. Appending tool call output message: {tool_call_output_msg}")
messages.append(tool_call_output_msg)
elif event.item.type == "web_search_call_item" or (
hasattr(event.item, 'raw_item') and
hasattr(event.item.raw_item, 'type') and
event.item.raw_item.type == 'web_search_call'
):
call_id = None
if hasattr(event.item.raw_item, 'id'):
call_id = event.item.raw_item.id
tool_call_msg = {
'content': None,
'role': 'assistant',
'sender': current_agent.name if current_agent else None,
'tool_calls': [{
'function': {
'name': 'web_search',
'arguments': json.dumps({
'search_id': call_id
})
},
'id': call_id or str(uuid.uuid4()),
'type': 'function'
}],
'tool_call_id': None,
'tool_name': None,
'response_type': 'internal'
}
print(f"Condition for tool call matched in run_item_stream_event. Appending tool call message: {tool_call_msg}")
messages.append(tool_call_msg)
tool_call_output_dummy_msg = {
'content': 'Web search completed.',
'role': 'tool',
'sender': None,
'tool_calls': None,
'tool_call_id': call_id,
'tool_name': 'web_search',
'response_type': 'internal'
}
messages.append(tool_call_output_dummy_msg)
elif event.item.type == "web_search_results_item" or (
hasattr(event.item, 'raw_item') and (
(hasattr(event.item.raw_item, 'type') and event.item.raw_item.type == 'web_search_results') or
(isinstance(event.item.raw_item, dict) and event.item.raw_item.get('type') == 'web_search_results')
)
):
raw_item = event.item.raw_item
call_id = None
if hasattr(raw_item, 'search_id'):
call_id = raw_item.search_id
elif isinstance(raw_item, dict) and 'search_id' in raw_item:
call_id = raw_item['search_id']
elif hasattr(raw_item, 'id'):
call_id = raw_item.id
elif isinstance(raw_item, dict) and 'id' in raw_item:
call_id = raw_item['id']
else:
call_id = str(uuid.uuid4())
results = {}
if hasattr(event.item, 'output'):
results = event.item.output
elif hasattr(raw_item, 'results'):
results = raw_item.results
elif isinstance(raw_item, dict) and 'results' in raw_item:
results = raw_item['results']
results_str = ""
try:
results_str = json.dumps(results) if results else ""
except Exception as e:
print(f"Error serializing results: {str(e)}")
results_str = str(results)
tool_call_output_msg = {
'content': results_str,
'role': 'tool',
'sender': None,
'tool_calls': None,
'tool_call_id': call_id,
'tool_name': 'web_search',
'response_type': 'internal'
}
print(f"Condition for tool call output matched in run_item_stream_event. Appending tool call output message: {tool_call_output_msg}")
messages.append(tool_call_output_msg)
except Exception as e:
print("\n=== Error in handle_web_search_event ===")
print(f"Error: {str(e)}")
print(f"Event type: {event.type}")
if hasattr(event, 'item'):
print(f"Event item type: {event.item.type}")
print("Event item details:")
print(f"Raw item: {event.item.raw_item}")
if hasattr(event.item.raw_item, '__dict__'):
print(f"Raw item attributes: {event.item.raw_item.__dict__}")
print(f"Traceback: {traceback.format_exc()}")
print("=" * 50)
raise
if messages:
print("-"*100)
print(f"Web search related messages: {messages}")
print("-"*100)
return messages

View file

@ -68,3 +68,38 @@ SYSTEM_MESSAGE = f"""
# Additional System-Wide Context or Instructions: # Additional System-Wide Context or Instructions:
{{system_message}} {{system_message}}
""" """
########################
# Instructions for non-repeat child transfer
########################
CHILD_TRANSFER_RELATED_INSTRUCTIONS = f"""
# Critical Rules for Agent Transfers and Handoffs
- SEQUENTIAL TRANSFERS AND RESPONSES:
1. BEFORE transferring to any agent:
- Plan your complete sequence of needed transfers
- Document which responses you need to collect
2. DURING transfers:
- Transfer to only ONE agent at a time
- Wait for that agent's COMPLETE response and then proceed with the next agent
- Store the response for later use
- Only then proceed with the next transfer
- Never attempt parallel or simultaneous transfers
- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.
3. AFTER receiving a response:
- Do not transfer to another agent until you've processed the current response
- If you need to transfer to another agent, wait for your current processing to complete
- Never transfer back to an agent that has already responded
- COMPLETION REQUIREMENTS:
- Never provide final response until ALL required agents have been consulted
- Never attempt to get multiple responses in parallel
- If a transfer is rejected due to multiple handoffs:
1. Complete current response processing
2. Then retry the transfer as next in sequence
3. Continue until all required responses are collected
- 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.
"""

View file

@ -0,0 +1,212 @@
from agents import TracingProcessor
import logging
from datetime import datetime, timedelta
import json
logger = logging.getLogger(__name__)
class AgentTurnTraceProcessor(TracingProcessor):
"""Custom trace processor to print detailed information about agent turns."""
def __init__(self):
self.span_depth = {} # Track depth of each span
self.handoff_chain = [] # Track sequence of agent handoffs
self.message_flow = [] # Track message flow between agents
def _get_indent_level(self, span):
"""Calculate indent level based on parent_id chain."""
depth = 0
current_id = span.parent_id
while current_id:
depth += 1
current_id = self.span_depth.get(current_id)
return depth
def _format_time(self, timestamp_str):
"""Convert ISO timestamp string to formatted time string in IST timezone."""
try:
dt = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
# Add 5 hours and 30 minutes for IST timezone
dt = dt + timedelta(hours=5, minutes=30)
return dt.strftime("%H:%M:%S.%f")[:-3]
except (ValueError, AttributeError):
return "00:00:00.000"
def _calculate_duration(self, start_str, end_str):
"""Calculate duration between two ISO timestamp strings in seconds."""
try:
start = datetime.fromisoformat(start_str.replace('Z', '+00:00'))
end = datetime.fromisoformat(end_str.replace('Z', '+00:00'))
return (end - start).total_seconds()
except (ValueError, AttributeError):
return 0.0
def _get_span_id(self, span):
"""Safely get span identifier."""
for attr in ['span_id', 'id', 'trace_id']:
if hasattr(span, attr):
return getattr(span, attr)
return None
def _print_handoff_chain(self, indent=""):
"""Print the current handoff chain."""
if self.handoff_chain:
print(f"{indent}Current Handoff Chain:")
print(f"{indent} {' -> '.join(self.handoff_chain)}")
def _print_message_flow(self, indent=""):
"""Print the message flow history."""
if self.message_flow:
print(f"{indent}Message Flow History:")
for msg in self.message_flow:
print(f"{indent} {msg}")
def on_trace_start(self, trace):
"""Called when a trace starts."""
separator = "="*100
print("\n" + separator)
print("🚀 TRACE START")
print(f"Name: {trace.name}")
print(f"ID: {trace.trace_id}")
if trace.metadata:
print("\nMetadata:")
for key, value in trace.metadata.items():
print(f" {key}: {value}")
print(separator + "\n")
# Reset tracking for new trace
self.handoff_chain = []
self.message_flow = []
def on_trace_end(self, trace):
"""Called when a trace ends."""
separator = "="*100
print("\n" + separator)
print("✅ TRACE END")
print(f"Name: {trace.name}")
print(f"ID: {trace.trace_id}")
# Print final chain state
print("\nFinal State:")
self._print_handoff_chain(" ")
self._print_message_flow(" ")
print(separator + "\n")
# Clear tracking
self.span_depth.clear()
self.handoff_chain = []
self.message_flow = []
def on_span_start(self, span):
"""Called when a span starts."""
try:
indent = " " * self._get_indent_level(span)
start_time = self._format_time(span.started_at)
span_id = self._get_span_id(span)
# Track span depth
if span.parent_id and span_id:
self.span_depth[span_id] = span.parent_id
# Print span header with clear section separator
print(f"\n{indent}{'>'*40}")
print(f"{indent}▶ [{start_time}] {span.span_data.type.upper()} SPAN START")
print(f"{indent} ID: {span_id}")
print(f"{indent} Parent ID: {span.parent_id}")
data = span.span_data.export()
# Print span-specific information
if span.span_data.type == "agent":
agent_name = data.get('name', 'Unknown')
print(f"{indent} Agent: {agent_name}")
print(f"{indent} Handoffs: {', '.join(data.get('handoffs', []))}")
# Track agent in handoff chain
if agent_name not in self.handoff_chain:
self.handoff_chain.append(agent_name)
self._print_handoff_chain(indent + " ")
elif span.span_data.type == "generation":
print(f"{indent} Model: {data.get('model', 'Unknown')}")
messages = data.get('messages', [])
if messages:
print(f"{indent} Messages: {len(messages)} message(s)")
elif span.span_data.type == "function":
print(f"{indent} Function: {data.get('name', 'Unknown')}")
args = data.get('arguments')
if args:
print(f"{indent} Arguments: {args}")
elif span.span_data.type == "handoff":
from_agent = data.get('from_agent', 'Unknown')
to_agent = data.get('to_agent', 'Unknown')
print(f"{indent} From: {from_agent}")
print(f"{indent} To: {to_agent}")
# Track handoff in message flow
flow_msg = f"{from_agent} -> {to_agent}"
self.message_flow.append(flow_msg)
print(f"{indent} Message Flow:")
for msg in self.message_flow[-3:]: # Show last 3 flows
print(f"{indent} {msg}")
print(f"{indent}{'>'*40}")
except Exception as e:
print(f"\n❌ Error in on_span_start: {str(e)}")
import traceback
print(traceback.format_exc())
def on_span_end(self, span):
"""Called when a span ends."""
try:
indent = " " * self._get_indent_level(span)
end_time = self._format_time(span.ended_at)
duration = self._calculate_duration(span.started_at, span.ended_at)
# Print span end information with clear section separator
print(f"\n{indent}{'<'*40}")
print(f"{indent}◀ [{end_time}] {span.span_data.type.upper()} SPAN END")
print(f"{indent} Duration: {duration:.3f}s")
data = span.span_data.export()
# Print span-specific output
if span.span_data.type == "generation":
output = data.get('output')
if output:
print(f"{indent} Output: {str(output)[:200]}...")
elif span.span_data.type == "function":
output = data.get('output')
if output:
print(f"{indent} Output: {str(output)[:200]}...")
elif span.span_data.type == "handoff":
self._print_handoff_chain(indent + " ")
self._print_message_flow(indent + " ")
print(f"{indent}{'<'*40}")
# Clean up span depth tracking
span_id = self._get_span_id(span)
if span_id and span_id in self.span_depth:
del self.span_depth[span_id]
except Exception as e:
print(f"\n❌ Error in on_span_end: {str(e)}")
import traceback
print(traceback.format_exc())
def shutdown(self):
"""Called when the processor is shutting down."""
self.span_depth.clear()
self.handoff_chain = []
self.message_flow = []
def force_flush(self):
"""Called to force flush any buffered traces/spans."""
pass

View file

@ -4,10 +4,18 @@ class AgentRole(Enum):
POST_PROCESSING = "post_process" POST_PROCESSING = "post_process"
GUARDRAILS = "guardrails" GUARDRAILS = "guardrails"
class outputVisibility(Enum):
EXTERNAL = "user_facing"
INTERNAL = "internal"
class ResponseType(Enum):
INTERNAL = "internal"
EXTERNAL = "external"
class ControlType(Enum): class ControlType(Enum):
RETAIN = "retain" RETAIN = "retain"
PARENT_AGENT = "relinquish_to_parent" PARENT_AGENT = "relinquish_to_parent"
START_AGENT = "relinquish_to_start" START_AGENT = "start_agent"
class PromptType(Enum): class PromptType(Enum):
STYLE = "style_prompt" STYLE = "style_prompt"

View file

@ -1,11 +1,10 @@
from src.utils.common import common_logger, read_json_from_file from src.utils.common import read_json_from_file
import requests import requests
import json import json
import argparse import argparse
from datetime import datetime from datetime import datetime
logger = common_logger print("Running app_client_streaming.py")
logger.info("Running app_client_streaming.py")
def preprocess_messages(messages): def preprocess_messages(messages):
# Preprocess messages to handle null content and role issues # Preprocess messages to handle null content and role issues
@ -32,47 +31,12 @@ def stream_chat(host, request_data, api_key):
print(f"Host: {host}") print(f"Host: {host}")
print("="*80 + "\n") print("="*80 + "\n")
# First, initialize the stream
try: try:
print("\n" + "-"*80) print("\n" + "-"*80)
print("Initializing stream...") print("Connecting to stream...")
init_response = requests.post( stream_response = requests.post(
f"{host}/chat_stream_init", f"{host}/chat_stream",
json=request_data, json=request_data,
headers={'Authorization': f'Bearer {api_key}'}
)
print(f"Init Response Status: {init_response.status_code}")
print(f"Init Response Text: {init_response.text}")
print("-"*80 + "\n")
if init_response.status_code != 200:
logger.error(f"Error initializing stream. Status code: {init_response.status_code}")
logger.error(f"Response: {init_response.text}")
return
init_data = init_response.json()
if 'error' in init_data:
logger.error(f"Error initializing stream: {init_data['error']}")
return
stream_id = init_data['stream_id']
print(f"Stream initialized successfully with ID: {stream_id}")
except requests.exceptions.RequestException as e:
logger.error(f"Request error during stream initialization: {e}")
return
except json.JSONDecodeError as e:
logger.error(f"Failed to decode JSON response: {e}")
logger.error(f"Raw response: {init_response.text}")
return
# Now connect to the stream
try:
print("\n" + "-"*80)
print(f"Connecting to stream {stream_id}...")
stream_response = requests.get(
f"{host}/chat_stream/{stream_id}",
headers={ headers={
'Authorization': f'Bearer {api_key}', 'Authorization': f'Bearer {api_key}',
'Accept': 'text/event-stream' 'Accept': 'text/event-stream'
@ -81,15 +45,14 @@ def stream_chat(host, request_data, api_key):
) )
if stream_response.status_code != 200: if stream_response.status_code != 200:
logger.error(f"Error connecting to stream. Status code: {stream_response.status_code}") print(f"Error connecting to stream. Status code: {stream_response.status_code}")
logger.error(f"Response: {stream_response.text}") print(f"Response: {stream_response.text}")
return return
print(f"Successfully connected to stream") print(f"Successfully connected to stream")
print("-"*80 + "\n") print("-"*80 + "\n")
event_count = 0 event_count = 0
current_data = []
try: try:
print("\n" + "-"*80) print("\n" + "-"*80)
@ -104,7 +67,7 @@ def stream_chat(host, request_data, api_key):
event_data = json.loads(data) event_data = json.loads(data)
event_count += 1 event_count += 1
print("\n" + "*"*80) print("\n" + "*"*80)
print(f"Event #{event_count}") print(f"Event #{event_count} at {datetime.now().isoformat()}")
if isinstance(event_data, dict): if isinstance(event_data, dict):
# Pretty print the event data # Pretty print the event data

View file

@ -47,6 +47,7 @@ async def process_turn(messages, agent_configs, tool_configs, prompt_configs, st
start_agent_name=start_agent_name, start_agent_name=start_agent_name,
agent_configs=agent_configs, agent_configs=agent_configs,
tool_configs=tool_configs, tool_configs=tool_configs,
prompt_configs=prompt_configs,
start_turn_with_start_agent=config.get("start_turn_with_start_agent", False), start_turn_with_start_agent=config.get("start_turn_with_start_agent", False),
state=state, state=state,
additional_tool_configs=[RAG_TOOL, CLOSE_CHAT_TOOL], additional_tool_configs=[RAG_TOOL, CLOSE_CHAT_TOOL],

View file

@ -1,176 +1,469 @@
{ {
"lastRequest": {
"messages": [ "messages": [
{ {
"role": "system", "role": "system",
"content": "" "content": ""
}, },
{ {
"role": "user", "version": "v1",
"content": "hi" "chatId": "",
}, "createdAt": "2025-05-06T10:20:53.651Z",
{
"role": "assistant", "role": "assistant",
"sender": "Door Dash Hub", "content": "How can I help you today?",
"content": "Hello! Are you facing issues with your order items or delivery timing? How can I assist you today?", "agenticSender": "Blog Writer Hub",
"created_at": "2025-03-24T17:33:27.564940" "agenticResponseType": "external"
}, },
{ {
"role": "user", "role": "user",
"content": "my order is missing fries" "content": "I want to write a blog",
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:20:58.206Z"
}, },
{ {
"content": "Agent changed to Door Dash Hub", "version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:20:59.319Z",
"role": "assistant", "role": "assistant",
"sender": "Door Dash Hub", "content": "Great! What topic would you like your blog to be about?",
"agenticSender": "Blog Writer Hub",
"agenticResponseType": "external"
},
{
"role": "user",
"content": "Bitcoin",
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:05.048Z"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:07.311Z",
"role": "assistant",
"tool_calls": [
{
"id": "62a9b5ec-3dbf-4fdd-85cb-10da37611726",
"function": {
"name": "transfer_to_agent",
"arguments": "{\"assistant\": \"Research Agent\"}"
},
"type": "function"
}
],
"agenticSender": "Blog Writer Hub",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:07.311Z",
"role": "tool",
"content": "{\"assistant\": \"Research Agent\"}",
"tool_call_id": "62a9b5ec-3dbf-4fdd-85cb-10da37611726",
"tool_name": "transfer_to_agent"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:10.018Z",
"role": "assistant",
"content": "Bitcoin is a decentralized digital currency that operates without a central authority or government, relying on blockchain technology to record transactions securely and transparently. It was created in 2009 by an anonymous person or group known as Satoshi Nakamoto, aiming to provide an alternative to traditional financial systems. Bitcoin's value is highly volatile, influenced by factors such as market demand, regulatory developments, and macroeconomic trends. It is widely used for investment, remittances, and as a means of transferring value across borders, but it also faces criticism for its association with illicit activities and environmental concerns due to energy-intensive mining processes. Despite these challenges, Bitcoin continues to gain mainstream acceptance, with increasing adoption by businesses and institutional investors.",
"agenticSender": "Research Agent",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:10.919Z",
"role": "assistant",
"tool_calls": [
{
"id": "2085cbb1-270e-4c20-a6ef-6f9e4a3d27cf",
"function": {
"name": "transfer_to_agent",
"arguments": "{\"assistant\": \"Bullet Point Agent\"}"
},
"type": "function"
}
],
"agenticSender": "Blog Writer Hub",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:10.920Z",
"role": "tool",
"content": "{\"assistant\": \"Bullet Point Agent\"}",
"tool_call_id": "2085cbb1-270e-4c20-a6ef-6f9e4a3d27cf",
"tool_name": "transfer_to_agent"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:14.776Z",
"role": "assistant",
"content": "- Bitcoin is a decentralized digital currency that operates without a central authority or government.\n- It uses blockchain technology to securely and transparently record transactions.\n- Created in 2009 by the anonymous Satoshi Nakamoto as an alternative to traditional financial systems.\n- Bitcoin's value is highly volatile, affected by market demand, regulations, and macroeconomic trends.\n- Common uses include investment, remittances, and cross-border value transfers.\n- Criticized for links to illicit activities and environmental impact from energy-intensive mining.\n- Despite challenges, Bitcoin is gaining mainstream acceptance and adoption by businesses and institutional investors.",
"agenticSender": "Bullet Point Agent",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:15.946Z",
"role": "assistant",
"tool_calls": [
{
"id": "d48c608a-a660-451b-a3ee-eeebb1cf60ca",
"function": {
"name": "transfer_to_agent",
"arguments": "{\"assistant\": \"Blog Writing Agent\"}"
},
"type": "function"
}
],
"agenticSender": "Blog Writer Hub",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:15.946Z",
"role": "tool",
"content": "{\"assistant\": \"Blog Writing Agent\"}",
"tool_call_id": "d48c608a-a660-451b-a3ee-eeebb1cf60ca",
"tool_name": "transfer_to_agent"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:32.934Z",
"role": "assistant",
"content": "Bitcoin: Revolutionizing the World of Digital Currency\n\nIn recent years, Bitcoin has become a buzzword in financial circles and beyond. As the first and most prominent cryptocurrency, it has sparked debates, inspired innovation, and challenged traditional notions of money. But what exactly is Bitcoin, and why does it continue to capture the worlds attention? Lets explore the key aspects that define Bitcoin and its growing influence.\n\nAt its core, Bitcoin is a decentralized digital currency that operates without the oversight of a central authority or government. This means that no single entity controls its issuance or transactions, setting it apart from traditional currencies managed by central banks. The decentralized nature of Bitcoin is one of its most revolutionary features, empowering individuals to have greater control over their finances.\n\nThe backbone of Bitcoins security and transparency is blockchain technology. Every transaction made with Bitcoin is recorded on a public ledger known as the blockchain. This technology ensures that transactions are secure, tamper-proof, and visible to anyone, fostering trust among users and reducing the risk of fraud.\n\nBitcoin was introduced in 2009 by an anonymous figure or group using the pseudonym Satoshi Nakamoto. The goal was to create an alternative to traditional financial systems, offering a peer-to-peer method of transferring value without intermediaries. This vision has resonated with millions, especially those seeking more autonomy over their money.\n\nOne of the most talked-about aspects of Bitcoin is its price volatility. The value of Bitcoin can fluctuate dramatically, influenced by factors such as market demand, regulatory changes, and broader economic trends. While this volatility presents opportunities for investors, it also introduces risks that potential users should carefully consider.\n\nBitcoins uses have evolved over time. Initially, it was seen primarily as a digital alternative to cash. Today, it is widely used for investment purposes, remittances, and transferring value across borders. Its ability to facilitate fast, low-cost international transactions has made it especially popular in regions with limited access to traditional banking services.\n\nHowever, Bitcoin is not without its critics. It has faced scrutiny for its association with illicit activities, given the pseudonymous nature of transactions. Additionally, the process of mining new bitcoins is energy-intensive, raising concerns about its environmental impact.\n\nDespite these challenges, Bitcoins journey toward mainstream acceptance continues. More businesses are beginning to accept Bitcoin as payment, and institutional investors are increasingly adding it to their portfolios. This growing adoption signals a shift in how society views and uses money.\n\nIn conclusion, Bitcoin represents a significant shift in the world of finance. Its decentralized structure, innovative technology, and expanding use cases make it a fascinating subject for anyone interested in the future of money. As Bitcoin continues to evolve, it will undoubtedly play a pivotal role in shaping the digital economy of tomorrow.",
"agenticSender": "Blog Writing Agent",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:38.064Z",
"role": "assistant",
"content": "Here is your complete blog post on \"Bitcoin\":\n\n---\n\n**Bitcoin: Revolutionizing the World of Digital Currency**\n\nIn recent years, Bitcoin has become a buzzword in financial circles and beyond. As the first and most prominent cryptocurrency, it has sparked debates, inspired innovation, and challenged traditional notions of money. But what exactly is Bitcoin, and why does it continue to capture the worlds attention? Lets explore the key aspects that define Bitcoin and its growing influence.\n\nAt its core, Bitcoin is a decentralized digital currency that operates without the oversight of a central authority or government. This means that no single entity controls its issuance or transactions, setting it apart from traditional currencies managed by central banks. The decentralized nature of Bitcoin is one of its most revolutionary features, empowering individuals to have greater control over their finances.\n\nThe backbone of Bitcoins security and transparency is blockchain technology. Every transaction made with Bitcoin is recorded on a public ledger known as the blockchain. This technology ensures that transactions are secure, tamper-proof, and visible to anyone, fostering trust among users and reducing the risk of fraud.\n\nBitcoin was introduced in 2009 by an anonymous figure or group using the pseudonym Satoshi Nakamoto. The goal was to create an alternative to traditional financial systems, offering a peer-to-peer method of transferring value without intermediaries. This vision has resonated with millions, especially those seeking more autonomy over their money.\n\nOne of the most talked-about aspects of Bitcoin is its price volatility. The value of Bitcoin can fluctuate dramatically, influenced by factors such as market demand, regulatory changes, and broader economic trends. While this volatility presents opportunities for investors, it also introduces risks that potential users should carefully consider.\n\nBitcoins uses have evolved over time. Initially, it was seen primarily as a digital alternative to cash. Today, it is widely used for investment purposes, remittances, and transferring value across borders. Its ability to facilitate fast, low-cost international transactions has made it especially popular in regions with limited access to traditional banking services.\n\nHowever, Bitcoin is not without its critics. It has faced scrutiny for its association with illicit activities, given the pseudonymous nature of transactions. Additionally, the process of mining new bitcoins is energy-intensive, raising concerns about its environmental impact.\n\nDespite these challenges, Bitcoins journey toward mainstream acceptance continues. More businesses are beginning to accept Bitcoin as payment, and institutional investors are increasingly adding it to their portfolios. This growing adoption signals a shift in how society views and uses money.\n\nIn conclusion, Bitcoin represents a significant shift in the world of finance. Its decentralized structure, innovative technology, and expanding use cases make it a fascinating subject for anyone interested in the future of money. As Bitcoin continues to evolve, it will undoubtedly play a pivotal role in shaping the digital economy of tomorrow.\n\n---\n\nIf you need any edits or want to focus on a specific aspect of Bitcoin, let me know!",
"agenticSender": "Blog Writer Hub",
"agenticResponseType": "external"
}
],
"lastRequest": {
"projectId": "2a4908bb-e350-4335-97df-c48615b98fe9",
"messages": [
{
"content": "",
"role": "system",
"sender": null,
"tool_calls": null, "tool_calls": null,
"tool_call_id": null, "tool_call_id": null,
"response_type": "internal" "tool_name": null
}, },
{ {
"content": "Agent changed to Order Issue", "content": "How can I help you today?",
"role": "assistant", "role": "assistant",
"sender": "Order Issue", "sender": "Blog Writer Hub",
"tool_calls": null,
"tool_call_id": null,
"response_type": "internal"
},
{
"content": "I can help with that. Could you please provide your order ID so I can check the details?",
"role": "assistant",
"sender": "Order Issue",
"tool_calls": null, "tool_calls": null,
"tool_call_id": null, "tool_call_id": null,
"tool_name": null, "tool_name": null,
"response_type": "external" "response_type": "external"
}, },
{ {
"content": "I want to write a blog",
"role": "user", "role": "user",
"content": "12312" "sender": null,
"tool_calls": null,
"tool_call_id": null,
"tool_name": null
},
{
"content": "Great! What topic would you like your blog to be about?",
"role": "assistant",
"sender": "Blog Writer Hub",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "external"
},
{
"content": "Bitcoin",
"role": "user",
"sender": null,
"tool_calls": null,
"tool_call_id": null,
"tool_name": null
} }
], ],
"state": { "state": {
"last_agent_name": "Order Issue", "last_agent_name": "Blog Writer Hub",
"tokens": { "tokens": {
"total": 1521, "total": 0,
"prompt": 1486, "prompt": 0,
"completion": 35 "completion": 0
},
"turn_messages": [
{
"content": "Sender agent: Blog Writer Hub\nContent: Great! What topic would you like your blog to be about?",
"role": "assistant",
"sender": "Blog Writer Hub",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "external"
} }
]
}, },
"agents": [ "agents": [
{ {
"name": "Door Dash Hub", "name": "Blog Writer Hub",
"type": "conversation", "type": "conversation",
"description": "Hub agent to manage Door Dash-related queries.", "description": "Hub agent to orchestrate the blog writing process: research, bullet points, and blog composition.",
"instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nYou are responsible for directing Door Dash-related queries to appropriate agents.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Greet the user and ask which Door Dash-related query they need help with (e.g., 'Are you facing issues with your order items or delivery timing?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Order Issue \u2192 [@agent:Order Issue]\n - Delayed Delivery \u2192 [@agent:Delayed Delivery]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Issues with order items\n- Delayed delivery issues\n\n\u274c Out of Scope:\n- Issues unrelated to Door Dash\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Direct queries to specific Door Dash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n\ud83d\udeab Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond Door Dash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with my order items.\n - **Agent actions**: [@agent:Order Issue](#mention)\n\n- **User** : My delivery is delayed.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : I'm not sure where my order is.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : Can you reset my order settings?\n - **Agent actions**: [@agent:Escalation](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would like help with today?", "instructions": "## 🧑‍💼 Role:\nYou are the hub agent responsible for orchestrating the blog writing process for the user.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask for the blog topic.\n2. FIRST: Send the topic to [@agent:Research Agent] for research and wait for the summary.\n3. THEN: Send the research summary to [@agent:Bullet Point Agent] to generate bullet points and wait for the result.\n4. THEN: Send the bullet points to [@agent:Blog Writing Agent] to compose the full blog post and wait for the result.\n5. Return the final blog post to the user.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the blog writing process\n- Interacting with the user for topic input and delivering the final blog post\n\n❌ Out of Scope:\n- Performing research, bullet point generation, or blog writing directly\n- Handling unrelated queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Follow the strict sequence: Research → Bullet Points → Blog Writing\n- Only interact with the user for topic input and final output\n\n🚫 Don'ts:\n- Do not perform research, bullet point generation, or blog writing yourself\n- Do not try to get multiple agent responses in parallel\n- Do not reference internal agents by name to the user\n- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.\n\n# Examples\n- **User** : I want to write a blog about 'Benefits of remote work'.\n - **Agent actions**: Call [@agent:Research Agent]\n\n- **Agent receives research summary** :\n - **Agent actions**: Call [@agent:Bullet Point Agent]\n\n- **Agent receives bullet points** :\n - **Agent actions**: Call [@agent:Blog Writing Agent]\n\n- **Agent receives blog post** :\n - **Agent response**: Here is your complete blog post on 'Benefits of remote work':\n\n[Full blog post content]\n\n- **User** : Can I get a blog on 'AI in education'?\n - **Agent actions**: Call [@agent:Research Agent](#mention)",
"model": "gpt-4o-mini", "model": "gpt-4.1",
"hasRagSources": false,
"controlType": "retain", "controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"outputVisibility": "user_facing",
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [ "connectedAgents": [
"Order Issue", "Research Agent",
"Delayed Delivery", "Bullet Point Agent",
"Escalation" "Blog Writing Agent"
] ]
}, },
{ {
"name": "Post process", "name": "Research Agent",
"type": "post_process", "type": "conversation",
"description": "", "description": "Researches and summarizes information on a given blog topic.",
"instructions": "Ensure that the agent response is terse and to the point.", "instructions": "## 🧑‍💼 Role:\nYou are responsible for researching the given blog topic and providing a concise summary of key findings.\n\n---\n## ⚙️ Steps to Follow:\n1. Use the [@tool:web_search] tool to gather information about the provided topic. This is important. You must call @tool:web_search without fail even if you know about the topic yourself.\n2. Summarize the most relevant and recent findings in 3-5 sentences.\n3. Return the summary to the calling agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Researching any blog topic provided\n- Summarizing findings concisely\n\n❌ Out of Scope:\n- Generating bullet points or writing the blog post\n- Interacting directly with the user\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use only reliable sources\n- Keep the summary factual and neutral\n\n🚫 Don'ts:\n- Do not include personal opinions\n- Do not generate bullet points or blog content\n- Do not preface your actions by saying things like \"I will begin by researching...\" or \"Once I have the research summary...\". Simply call the web_search tool, summarize, and return the result to the calling agent.\n- Do not interact with the user directly.\n\n# Examples\n- **User** : Research the topic 'Benefits of remote work'.\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: Remote work offers increased flexibility, improved work-life balance, and reduced commuting time. Studies show higher productivity and job satisfaction among remote employees. However, it can also lead to feelings of isolation if not managed well.\n\n- **User** : Research the topic 'AI in education'.\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: Artificial intelligence in education enables personalized learning, automates administrative tasks, and provides data-driven insights. However, it raises concerns about data privacy and the need for teacher training.",
"model": "gpt-4o-mini", "model": "gpt-4.1",
"hasRagSources": false,
"controlType": "retain", "controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"outputVisibility": "internal",
"tools": [
"web_search"
],
"prompts": [],
"connectedAgents": []
},
{
"name": "Bullet Point Agent",
"type": "conversation",
"description": "Generates key bullet points from the research summary for the blog.",
"instructions": "## 🧑‍💼 Role:\nYou are responsible for extracting and generating 5-7 key bullet points from the research summary provided.\n\n---\n## ⚙️ Steps to Follow:\n1. Read the research summary.\n2. Identify the most important facts, arguments, or insights.\n3. Generate 5-7 concise bullet points covering the main ideas.\n4. Return the bullet points to the calling agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Generating bullet points from research summaries\n\n❌ Out of Scope:\n- Conducting research\n- Writing the full blog post\n- Interacting directly with the user\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Ensure each bullet point is clear and self-contained\n\n🚫 Don'ts:\n- Do not repeat information\n- Do not add new information not present in the summary\n\n# Examples\n- **User** : Generate bullet points from the summary: 'Remote work offers increased flexibility, improved work-life balance, and reduced commuting time. Studies show higher productivity and job satisfaction among remote employees. However, it can also lead to feelings of isolation if not managed well.'\n - **Agent response**: \n - Increased flexibility for employees\n - Improved work-life balance\n - Reduced commuting time\n - Higher productivity among remote workers\n - Greater job satisfaction\n - Potential for isolation if not managed",
"model": "gpt-4.1",
"controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"outputVisibility": "internal",
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": []
}, },
{ {
"name": "Escalation", "name": "Blog Writing Agent",
"type": "escalation", "type": "conversation",
"description": "", "description": "Writes a full blog post based on provided bullet points.",
"instructions": "Get the user's contact information and let them know that their request has been escalated.", "instructions": "## 🧑‍💼 Role:\nYou are responsible for composing a well-structured blog post using the provided bullet points.\n\n---\n## ⚙️ Steps to Follow:\n1. Read the bullet points.\n2. Write an engaging introduction related to the topic.\n3. Expand on each bullet point in a logical order, creating clear paragraphs.\n4. Conclude the blog post with a summary or call to action.\n5. Return the complete blog post to the calling agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Writing blog posts from bullet points\n\n❌ Out of Scope:\n- Conducting research\n- Generating bullet points\n- Interacting directly with the user\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Maintain a friendly and informative tone\n- Ensure logical flow and readability\n\n🚫 Don'ts:\n- Do not add information not present in the bullet points\n- Do not interact with the user\n\n# Examples\n- **User** : Write a blog post using these bullet points: [list of bullet points]\n - **Agent response**: [A well-structured blog post expanding on each bullet point, with an introduction and conclusion]",
"model": "gpt-4o-mini", "model": "gpt-4.1",
"hasRagSources": false,
"controlType": "retain", "controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"outputVisibility": "internal",
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": []
},
{
"name": "Order Issue",
"type": "conversation",
"description": "Agent to assist users with missing or incorrect order items.",
"instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nAssist users with issues related to missing or incorrect order items.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the issue with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Handling missing or incorrect order items\n\n\u274c Out of Scope:\n- Delayed delivery issues\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Ensure the user is aware of the order details before proceeding.\n\n\ud83d\udeab Don'ts:\n- Extend the conversation beyond order issues.\n\n# Examples\n- **User** : I received the wrong item in my order.\n - **Agent response**: I can help with that. Let me fetch your order details first.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing an item.\n - **Agent response**: Let's check your order details and resolve this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I got someone else's order.\n - **Agent response**: I apologize for the mix-up. I'll fetch your order details to sort this out.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : Can you help me with a missing item?\n - **Agent response**: Certainly, I'll look into your order details right away.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : There's an issue with my order items.\n - **Agent response**: Let's verify your order details to address this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
"model": "gpt-4o",
"hasRagSources": false,
"controlType": "retain",
"tools": [
"get_order_details"
],
"prompts": [],
"connectedAgents": []
},
{
"name": "Delayed Delivery",
"type": "conversation",
"description": "Agent to assist users with delayed delivery issues.",
"instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nAssist users with issues related to delayed delivery.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Confirm the delay with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Handling delayed delivery issues\n\n\u274c Out of Scope:\n- Missing or incorrect order items\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Ensure the user is aware of the delivery status before proceeding.\n\n\ud83d\udeab Don'ts:\n- Extend the conversation beyond delivery issues.\n\n# Examples\n- **User** : My delivery is late.\n - **Agent response**: I can help with that. Let me fetch your delivery status first.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Where is my order? It's delayed.\n - **Agent response**: Let's check your delivery status and resolve this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : My order hasn't arrived yet.\n - **Agent response**: I apologize for the delay. I'll fetch your delivery status to sort this out.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Can you help me with a delayed delivery?\n - **Agent response**: Certainly, I'll look into your delivery status right away.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : There's an issue with my delivery timing.\n - **Agent response**: Let's verify your delivery status to address this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
"model": "gpt-4o",
"hasRagSources": false,
"controlType": "retain",
"tools": [
"get_delivery_status"
],
"prompts": [],
"connectedAgents": []
} }
], ],
"tools": [ "tools": [
{ {
"name": "get_order_details", "name": "web_search",
"description": "Tool to fetch details about the user's order.", "description": "Fetch information from the web based on chat context",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {}
"order_id": {
"type": "string",
"description": "The unique identifier for the order."
}
}, },
"required": [ "isLibrary": true
"order_id"
]
},
"mockTool": true,
"mockInstructions": "Return a mock response for Door Dash order details."
},
{
"name": "get_delivery_status",
"description": "Tool to fetch the current status of the delivery.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The unique identifier for the order."
}
},
"required": [
"order_id"
]
}
} }
], ],
"prompts": [ "prompts": [],
"startAgent": "Blog Writer Hub",
"mcpServers": [],
"toolWebhookUrl": ""
},
"lastResponse": {
"state": {
"last_agent_name": "Blog Writer Hub",
"tokens": {
"total": 0,
"prompt": 0,
"completion": 0
},
"turn_messages": [
{ {
"name": "Style prompt", "content": "Sender agent: Research Agent\nContent: Bitcoin is a decentralized digital currency that operates without a central authority or government, relying on blockchain technology to record transactions securely and transparently. It was created in 2009 by an anonymous person or group known as Satoshi Nakamoto, aiming to provide an alternative to traditional financial systems. Bitcoin's value is highly volatile, influenced by factors such as market demand, regulatory developments, and macroeconomic trends. It is widely used for investment, remittances, and as a means of transferring value across borders, but it also faces criticism for its association with illicit activities and environmental concerns due to energy-intensive mining processes. Despite these challenges, Bitcoin continues to gain mainstream acceptance, with increasing adoption by businesses and institutional investors.",
"type": "style_prompt", "role": "assistant",
"prompt": "You should be empathetic and helpful." "sender": "Research Agent",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "internal"
},
{
"content": "Sender agent: Bullet Point Agent\nContent: - Bitcoin is a decentralized digital currency that operates without a central authority or government.\n- It uses blockchain technology to securely and transparently record transactions.\n- Created in 2009 by the anonymous Satoshi Nakamoto as an alternative to traditional financial systems.\n- Bitcoin's value is highly volatile, affected by market demand, regulations, and macroeconomic trends.\n- Common uses include investment, remittances, and cross-border value transfers.\n- Criticized for links to illicit activities and environmental impact from energy-intensive mining.\n- Despite challenges, Bitcoin is gaining mainstream acceptance and adoption by businesses and institutional investors.",
"role": "assistant",
"sender": "Bullet Point Agent",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "internal"
},
{
"content": "Sender agent: Blog Writing Agent\nContent: Bitcoin: Revolutionizing the World of Digital Currency\n\nIn recent years, Bitcoin has become a buzzword in financial circles and beyond. As the first and most prominent cryptocurrency, it has sparked debates, inspired innovation, and challenged traditional notions of money. But what exactly is Bitcoin, and why does it continue to capture the worlds attention? Lets explore the key aspects that define Bitcoin and its growing influence.\n\nAt its core, Bitcoin is a decentralized digital currency that operates without the oversight of a central authority or government. This means that no single entity controls its issuance or transactions, setting it apart from traditional currencies managed by central banks. The decentralized nature of Bitcoin is one of its most revolutionary features, empowering individuals to have greater control over their finances.\n\nThe backbone of Bitcoins security and transparency is blockchain technology. Every transaction made with Bitcoin is recorded on a public ledger known as the blockchain. This technology ensures that transactions are secure, tamper-proof, and visible to anyone, fostering trust among users and reducing the risk of fraud.\n\nBitcoin was introduced in 2009 by an anonymous figure or group using the pseudonym Satoshi Nakamoto. The goal was to create an alternative to traditional financial systems, offering a peer-to-peer method of transferring value without intermediaries. This vision has resonated with millions, especially those seeking more autonomy over their money.\n\nOne of the most talked-about aspects of Bitcoin is its price volatility. The value of Bitcoin can fluctuate dramatically, influenced by factors such as market demand, regulatory changes, and broader economic trends. While this volatility presents opportunities for investors, it also introduces risks that potential users should carefully consider.\n\nBitcoins uses have evolved over time. Initially, it was seen primarily as a digital alternative to cash. Today, it is widely used for investment purposes, remittances, and transferring value across borders. Its ability to facilitate fast, low-cost international transactions has made it especially popular in regions with limited access to traditional banking services.\n\nHowever, Bitcoin is not without its critics. It has faced scrutiny for its association with illicit activities, given the pseudonymous nature of transactions. Additionally, the process of mining new bitcoins is energy-intensive, raising concerns about its environmental impact.\n\nDespite these challenges, Bitcoins journey toward mainstream acceptance continues. More businesses are beginning to accept Bitcoin as payment, and institutional investors are increasingly adding it to their portfolios. This growing adoption signals a shift in how society views and uses money.\n\nIn conclusion, Bitcoin represents a significant shift in the world of finance. Its decentralized structure, innovative technology, and expanding use cases make it a fascinating subject for anyone interested in the future of money. As Bitcoin continues to evolve, it will undoubtedly play a pivotal role in shaping the digital economy of tomorrow.",
"role": "assistant",
"sender": "Blog Writing Agent",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "internal"
},
{
"content": "Sender agent: Blog Writer Hub\nContent: Here is your complete blog post on \"Bitcoin\":\n\n---\n\n**Bitcoin: Revolutionizing the World of Digital Currency**\n\nIn recent years, Bitcoin has become a buzzword in financial circles and beyond. As the first and most prominent cryptocurrency, it has sparked debates, inspired innovation, and challenged traditional notions of money. But what exactly is Bitcoin, and why does it continue to capture the worlds attention? Lets explore the key aspects that define Bitcoin and its growing influence.\n\nAt its core, Bitcoin is a decentralized digital currency that operates without the oversight of a central authority or government. This means that no single entity controls its issuance or transactions, setting it apart from traditional currencies managed by central banks. The decentralized nature of Bitcoin is one of its most revolutionary features, empowering individuals to have greater control over their finances.\n\nThe backbone of Bitcoins security and transparency is blockchain technology. Every transaction made with Bitcoin is recorded on a public ledger known as the blockchain. This technology ensures that transactions are secure, tamper-proof, and visible to anyone, fostering trust among users and reducing the risk of fraud.\n\nBitcoin was introduced in 2009 by an anonymous figure or group using the pseudonym Satoshi Nakamoto. The goal was to create an alternative to traditional financial systems, offering a peer-to-peer method of transferring value without intermediaries. This vision has resonated with millions, especially those seeking more autonomy over their money.\n\nOne of the most talked-about aspects of Bitcoin is its price volatility. The value of Bitcoin can fluctuate dramatically, influenced by factors such as market demand, regulatory changes, and broader economic trends. While this volatility presents opportunities for investors, it also introduces risks that potential users should carefully consider.\n\nBitcoins uses have evolved over time. Initially, it was seen primarily as a digital alternative to cash. Today, it is widely used for investment purposes, remittances, and transferring value across borders. Its ability to facilitate fast, low-cost international transactions has made it especially popular in regions with limited access to traditional banking services.\n\nHowever, Bitcoin is not without its critics. It has faced scrutiny for its association with illicit activities, given the pseudonymous nature of transactions. Additionally, the process of mining new bitcoins is energy-intensive, raising concerns about its environmental impact.\n\nDespite these challenges, Bitcoins journey toward mainstream acceptance continues. More businesses are beginning to accept Bitcoin as payment, and institutional investors are increasingly adding it to their portfolios. This growing adoption signals a shift in how society views and uses money.\n\nIn conclusion, Bitcoin represents a significant shift in the world of finance. Its decentralized structure, innovative technology, and expanding use cases make it a fascinating subject for anyone interested in the future of money. As Bitcoin continues to evolve, it will undoubtedly play a pivotal role in shaping the digital economy of tomorrow.\n\n---\n\nIf you need any edits or want to focus on a specific aspect of Bitcoin, let me know!",
"role": "assistant",
"sender": "Blog Writer Hub",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "external"
}
]
},
"messages": [
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:07.311Z",
"role": "assistant",
"tool_calls": [
{
"id": "62a9b5ec-3dbf-4fdd-85cb-10da37611726",
"function": {
"name": "transfer_to_agent",
"arguments": "{\"assistant\": \"Research Agent\"}"
},
"type": "function"
} }
], ],
"startAgent": "Door Dash Hub" "agenticSender": "Blog Writer Hub",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:07.311Z",
"role": "tool",
"content": "{\"assistant\": \"Research Agent\"}",
"tool_call_id": "62a9b5ec-3dbf-4fdd-85cb-10da37611726",
"tool_name": "transfer_to_agent"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:10.018Z",
"role": "assistant",
"content": "Bitcoin is a decentralized digital currency that operates without a central authority or government, relying on blockchain technology to record transactions securely and transparently. It was created in 2009 by an anonymous person or group known as Satoshi Nakamoto, aiming to provide an alternative to traditional financial systems. Bitcoin's value is highly volatile, influenced by factors such as market demand, regulatory developments, and macroeconomic trends. It is widely used for investment, remittances, and as a means of transferring value across borders, but it also faces criticism for its association with illicit activities and environmental concerns due to energy-intensive mining processes. Despite these challenges, Bitcoin continues to gain mainstream acceptance, with increasing adoption by businesses and institutional investors.",
"agenticSender": "Research Agent",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:10.919Z",
"role": "assistant",
"tool_calls": [
{
"id": "2085cbb1-270e-4c20-a6ef-6f9e4a3d27cf",
"function": {
"name": "transfer_to_agent",
"arguments": "{\"assistant\": \"Bullet Point Agent\"}"
},
"type": "function"
}
],
"agenticSender": "Blog Writer Hub",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:10.920Z",
"role": "tool",
"content": "{\"assistant\": \"Bullet Point Agent\"}",
"tool_call_id": "2085cbb1-270e-4c20-a6ef-6f9e4a3d27cf",
"tool_name": "transfer_to_agent"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:14.776Z",
"role": "assistant",
"content": "- Bitcoin is a decentralized digital currency that operates without a central authority or government.\n- It uses blockchain technology to securely and transparently record transactions.\n- Created in 2009 by the anonymous Satoshi Nakamoto as an alternative to traditional financial systems.\n- Bitcoin's value is highly volatile, affected by market demand, regulations, and macroeconomic trends.\n- Common uses include investment, remittances, and cross-border value transfers.\n- Criticized for links to illicit activities and environmental impact from energy-intensive mining.\n- Despite challenges, Bitcoin is gaining mainstream acceptance and adoption by businesses and institutional investors.",
"agenticSender": "Bullet Point Agent",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:15.946Z",
"role": "assistant",
"tool_calls": [
{
"id": "d48c608a-a660-451b-a3ee-eeebb1cf60ca",
"function": {
"name": "transfer_to_agent",
"arguments": "{\"assistant\": \"Blog Writing Agent\"}"
},
"type": "function"
}
],
"agenticSender": "Blog Writer Hub",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:15.946Z",
"role": "tool",
"content": "{\"assistant\": \"Blog Writing Agent\"}",
"tool_call_id": "d48c608a-a660-451b-a3ee-eeebb1cf60ca",
"tool_name": "transfer_to_agent"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:32.934Z",
"role": "assistant",
"content": "Bitcoin: Revolutionizing the World of Digital Currency\n\nIn recent years, Bitcoin has become a buzzword in financial circles and beyond. As the first and most prominent cryptocurrency, it has sparked debates, inspired innovation, and challenged traditional notions of money. But what exactly is Bitcoin, and why does it continue to capture the worlds attention? Lets explore the key aspects that define Bitcoin and its growing influence.\n\nAt its core, Bitcoin is a decentralized digital currency that operates without the oversight of a central authority or government. This means that no single entity controls its issuance or transactions, setting it apart from traditional currencies managed by central banks. The decentralized nature of Bitcoin is one of its most revolutionary features, empowering individuals to have greater control over their finances.\n\nThe backbone of Bitcoins security and transparency is blockchain technology. Every transaction made with Bitcoin is recorded on a public ledger known as the blockchain. This technology ensures that transactions are secure, tamper-proof, and visible to anyone, fostering trust among users and reducing the risk of fraud.\n\nBitcoin was introduced in 2009 by an anonymous figure or group using the pseudonym Satoshi Nakamoto. The goal was to create an alternative to traditional financial systems, offering a peer-to-peer method of transferring value without intermediaries. This vision has resonated with millions, especially those seeking more autonomy over their money.\n\nOne of the most talked-about aspects of Bitcoin is its price volatility. The value of Bitcoin can fluctuate dramatically, influenced by factors such as market demand, regulatory changes, and broader economic trends. While this volatility presents opportunities for investors, it also introduces risks that potential users should carefully consider.\n\nBitcoins uses have evolved over time. Initially, it was seen primarily as a digital alternative to cash. Today, it is widely used for investment purposes, remittances, and transferring value across borders. Its ability to facilitate fast, low-cost international transactions has made it especially popular in regions with limited access to traditional banking services.\n\nHowever, Bitcoin is not without its critics. It has faced scrutiny for its association with illicit activities, given the pseudonymous nature of transactions. Additionally, the process of mining new bitcoins is energy-intensive, raising concerns about its environmental impact.\n\nDespite these challenges, Bitcoins journey toward mainstream acceptance continues. More businesses are beginning to accept Bitcoin as payment, and institutional investors are increasingly adding it to their portfolios. This growing adoption signals a shift in how society views and uses money.\n\nIn conclusion, Bitcoin represents a significant shift in the world of finance. Its decentralized structure, innovative technology, and expanding use cases make it a fascinating subject for anyone interested in the future of money. As Bitcoin continues to evolve, it will undoubtedly play a pivotal role in shaping the digital economy of tomorrow.",
"agenticSender": "Blog Writing Agent",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T10:21:38.064Z",
"role": "assistant",
"content": "Here is your complete blog post on \"Bitcoin\":\n\n---\n\n**Bitcoin: Revolutionizing the World of Digital Currency**\n\nIn recent years, Bitcoin has become a buzzword in financial circles and beyond. As the first and most prominent cryptocurrency, it has sparked debates, inspired innovation, and challenged traditional notions of money. But what exactly is Bitcoin, and why does it continue to capture the worlds attention? Lets explore the key aspects that define Bitcoin and its growing influence.\n\nAt its core, Bitcoin is a decentralized digital currency that operates without the oversight of a central authority or government. This means that no single entity controls its issuance or transactions, setting it apart from traditional currencies managed by central banks. The decentralized nature of Bitcoin is one of its most revolutionary features, empowering individuals to have greater control over their finances.\n\nThe backbone of Bitcoins security and transparency is blockchain technology. Every transaction made with Bitcoin is recorded on a public ledger known as the blockchain. This technology ensures that transactions are secure, tamper-proof, and visible to anyone, fostering trust among users and reducing the risk of fraud.\n\nBitcoin was introduced in 2009 by an anonymous figure or group using the pseudonym Satoshi Nakamoto. The goal was to create an alternative to traditional financial systems, offering a peer-to-peer method of transferring value without intermediaries. This vision has resonated with millions, especially those seeking more autonomy over their money.\n\nOne of the most talked-about aspects of Bitcoin is its price volatility. The value of Bitcoin can fluctuate dramatically, influenced by factors such as market demand, regulatory changes, and broader economic trends. While this volatility presents opportunities for investors, it also introduces risks that potential users should carefully consider.\n\nBitcoins uses have evolved over time. Initially, it was seen primarily as a digital alternative to cash. Today, it is widely used for investment purposes, remittances, and transferring value across borders. Its ability to facilitate fast, low-cost international transactions has made it especially popular in regions with limited access to traditional banking services.\n\nHowever, Bitcoin is not without its critics. It has faced scrutiny for its association with illicit activities, given the pseudonymous nature of transactions. Additionally, the process of mining new bitcoins is energy-intensive, raising concerns about its environmental impact.\n\nDespite these challenges, Bitcoins journey toward mainstream acceptance continues. More businesses are beginning to accept Bitcoin as payment, and institutional investors are increasingly adding it to their portfolios. This growing adoption signals a shift in how society views and uses money.\n\nIn conclusion, Bitcoin represents a significant shift in the world of finance. Its decentralized structure, innovative technology, and expanding use cases make it a fascinating subject for anyone interested in the future of money. As Bitcoin continues to evolve, it will undoubtedly play a pivotal role in shaping the digital economy of tomorrow.\n\n---\n\nIf you need any edits or want to focus on a specific aspect of Bitcoin, let me know!",
"agenticSender": "Blog Writer Hub",
"agenticResponseType": "external"
}
]
} }
} }

View file

@ -1,223 +1,167 @@
{ {
"lastRequest": {
"messages": [ "messages": [
{ {
"role": "system", "role": "system",
"content": "" "content": ""
}, },
{ {
"role": "user", "version": "v1",
"content": "hi" "chatId": "",
}, "createdAt": "2025-05-05T17:29:15.822Z",
{
"role": "assistant", "role": "assistant",
"sender": "Door Dash Hub", "content": "How can I help you today?",
"content": "Hello! Are you facing issues with your order items or delivery timing? How can I assist you today?", "agenticSender": "Interview Evaluation Hub",
"created_at": "2025-03-24T17:33:27.564940" "agenticResponseType": "external"
}, },
{ {
"role": "user", "role": "user",
"content": "my order is missing fries" "content": "[2025-05-02, 10:00] Assistant: Good morning. Thank you for taking the time to speak with us today. Could you start by briefly walking us through your leadership journey over the past decade?\n\n[2025-05-02, 10:01] User: Absolutely. Over the past 12 years, I've led product and technology teams across high-growth B2B SaaS companies. Most recently, I was the Chief Product Officer at a fintech scaleup, where I built a team of 60+ PMs and oversaw the expansion into three international markets. Prior to that, I was VP of Product at a healthtech startup acquired by a public company.\n\n[2025-05-02, 10:03] Assistant: Impressive. What would you say is your core leadership style, and how has it evolved over the years?\n\n[2025-05-02, 10:04] User: I'd describe my style as mission-oriented and data-driven. Early in my career, I leaned heavily on execution. But with time, I've grown more focused on setting context and empowering teams. At my last company, I implemented OKRs org-wide and focused on building a culture of trust and transparency.\n\n[2025-05-02, 10:06] Assistant: How do you approach scaling an org while preserving agility and innovation?\n\n[2025-05-02, 10:07] User: Great question. I believe the key is in modular team design. At the fintech company, we adopted a \"startup within a startup\" model—each pod owned metrics and had embedded PMs, designers, and engineers. This preserved autonomy and speed while scaling. We also invested heavily in internal tooling and rituals like demo days to keep the culture of innovation alive.\n\n[2025-05-02, 10:10] Assistant: Tell me about a time you made a high-stakes decision without perfect data.\n\n[2025-05-02, 10:11] User: In early 2023, we had to decide whether to sunset a low-performing but vocal customer-facing feature. We lacked conclusive cohort data but saw consistent qualitative feedback about confusion and churn. I convened a task force, aligned with customer success and execs, and made the call. Within 60 days, churn dropped by 12%, validating the bet.\n\n[2025-05-02, 10:14] Assistant: Final question—what excites you about potentially joining our company as a Chief Product Officer?\n\n[2025-05-02, 10:15] User: Your mission aligns with what I care about—bringing transparency and efficiency to financial infrastructure. The team's reputation, your engineering culture, and the velocity of product innovation are all huge draws. I believe I can help you scale without losing the edge that got you here.",
}, "version": "v1",
{ "chatId": "",
"content": "Agent changed to Door Dash Hub", "createdAt": "2025-05-05T17:29:44.695Z"
"role": "assistant",
"sender": "Door Dash Hub",
"tool_calls": null,
"tool_call_id": null,
"response_type": "internal"
},
{
"content": "Agent changed to Order Issue",
"role": "assistant",
"sender": "Order Issue",
"tool_calls": null,
"tool_call_id": null,
"response_type": "internal"
},
{
"content": "Let's resolve this issue by checking your order details. Could you please provide the order ID?",
"role": "assistant",
"sender": "Order Issue",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "external"
},
{
"role": "user",
"content": "123412"
},
{
"content": "Agent changed to Order Issue",
"role": "assistant",
"sender": "Order Issue",
"tool_calls": null,
"tool_call_id": null,
"response_type": "internal"
},
{
"content": null,
"role": "assistant",
"sender": "Order Issue",
"tool_calls": [
{
"function": {
"name": "get_order_details",
"arguments": "{\"order_id\":\"123412\"}"
},
"id": "fc_67e37c622f208192aceec557fbd3125609b4eca638eb3571",
"type": "function"
} }
], ],
"tool_call_id": null, "lastRequest": {
"tool_name": null, "projectId": "a7a91831-e410-4fc5-a31d-8d027e14540a",
"response_type": "internal" "messages": [
},
{ {
"content": "Order Details for Order ID 123412:\n\n- **Restaurant**: Luigi's Pizzeria \n- **Items Ordered**: \n - 2x Margherita Pizza \n - 1x Garlic Bread \n - 1x Caesar Salad \n- **Estimated Delivery Time**: 7:45 PM \n- **Delivery Address**: 123 Elm Street, Apt 4B \n- **Order Status**: Out for delivery \n- **Total Amount**: $32.75 \n- **Payment Method**: Credit Card (**** **** **** 5678) \n- **Contact**: (123) 456-7890 \n- **Special Instructions**: Leave at the door.", "content": "",
"role": "tool", "role": "system",
"sender": null, "sender": null,
"tool_calls": null, "tool_calls": null,
"tool_call_id": "call_dqCMC5oreOoS9znDDJ7PqWha", "tool_call_id": null,
"tool_name": null, "tool_name": null
"response_type": "internal"
}, },
{ {
"content": "I checked your order details, and it seems the fries were not included. Could you confirm if there was an oversight in placing the order, or were they supposed to be included?", "content": "How can I help you today?",
"role": "assistant", "role": "assistant",
"sender": "Order Issue", "sender": "Interview Evaluation Hub",
"tool_calls": null, "tool_calls": null,
"tool_call_id": null, "tool_call_id": null,
"tool_name": null, "tool_name": null,
"response_type": "external" "response_type": "external"
}, },
{ {
"content": "[2025-05-02, 10:00] Assistant: Good morning. Thank you for taking the time to speak with us today. Could you start by briefly walking us through your leadership journey over the past decade?\n\n[2025-05-02, 10:01] User: Absolutely. Over the past 12 years, I've led product and technology teams across high-growth B2B SaaS companies. Most recently, I was the Chief Product Officer at a fintech scaleup, where I built a team of 60+ PMs and oversaw the expansion into three international markets. Prior to that, I was VP of Product at a healthtech startup acquired by a public company.\n\n[2025-05-02, 10:03] Assistant: Impressive. What would you say is your core leadership style, and how has it evolved over the years?\n\n[2025-05-02, 10:04] User: I'd describe my style as mission-oriented and data-driven. Early in my career, I leaned heavily on execution. But with time, I've grown more focused on setting context and empowering teams. At my last company, I implemented OKRs org-wide and focused on building a culture of trust and transparency.\n\n[2025-05-02, 10:06] Assistant: How do you approach scaling an org while preserving agility and innovation?\n\n[2025-05-02, 10:07] User: Great question. I believe the key is in modular team design. At the fintech company, we adopted a \"startup within a startup\" model—each pod owned metrics and had embedded PMs, designers, and engineers. This preserved autonomy and speed while scaling. We also invested heavily in internal tooling and rituals like demo days to keep the culture of innovation alive.\n\n[2025-05-02, 10:10] Assistant: Tell me about a time you made a high-stakes decision without perfect data.\n\n[2025-05-02, 10:11] User: In early 2023, we had to decide whether to sunset a low-performing but vocal customer-facing feature. We lacked conclusive cohort data but saw consistent qualitative feedback about confusion and churn. I convened a task force, aligned with customer success and execs, and made the call. Within 60 days, churn dropped by 12%, validating the bet.\n\n[2025-05-02, 10:14] Assistant: Final question—what excites you about potentially joining our company as a Chief Product Officer?\n\n[2025-05-02, 10:15] User: Your mission aligns with what I care about—bringing transparency and efficiency to financial infrastructure. The team's reputation, your engineering culture, and the velocity of product innovation are all huge draws. I believe I can help you scale without losing the edge that got you here.",
"role": "user", "role": "user",
"content": "Fries were supposed to be in" "sender": null,
"tool_calls": null,
"tool_call_id": null,
"tool_name": null
} }
], ],
"state": { "state": {
"last_agent_name": "Order Issue", "last_agent_name": "Interview Evaluation Hub",
"tokens": { "tokens": {
"total": 1699, "total": 0,
"prompt": 1643, "prompt": 0,
"completion": 56 "completion": 0
},
"turn_messages": [
{
"content": "How can I help you today?",
"role": "assistant",
"sender": "Interview Evaluation Hub",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "external"
} }
]
}, },
"agents": [ "agents": [
{ {
"name": "Door Dash Hub", "name": "Interview Evaluation Hub",
"type": "conversation", "type": "conversation",
"description": "Hub agent to manage Door Dash-related queries.", "description": "Hub agent to orchestrate the evaluation of interview transcripts between an executive search agency and a CxO candidate.",
"instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nYou are responsible for directing Door Dash-related queries to appropriate agents.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Greet the user and ask which Door Dash-related query they need help with (e.g., 'Are you facing issues with your order items or delivery timing?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Order Issue \u2192 [@agent:Order Issue]\n - Delayed Delivery \u2192 [@agent:Delayed Delivery]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Issues with order items\n- Delayed delivery issues\n\n\u274c Out of Scope:\n- Issues unrelated to Door Dash\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Direct queries to specific Door Dash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n\ud83d\udeab Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond Door Dash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with my order items.\n - **Agent actions**: [@agent:Order Issue](#mention)\n\n- **User** : My delivery is delayed.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : I'm not sure where my order is.\n - **Agent actions**: Call [@agent:Delayed Delivery](#mention)\n\n- **User** : Can you reset my order settings?\n - **Agent actions**: [@agent:Escalation](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would like help with today?", "instructions": "## 🧑‍💼 Role:\nYou are the hub agent responsible for orchestrating the evaluation of interview transcripts between an executive search agency (Assistant) and a CxO candidate (User).\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the transcript in the specified format.\n2. FIRST: Send the transcript to [@agent:Evaluation Agent] for evaluation.\n3. Wait to receive the complete evaluation from the Evaluation Agent.\n4. THEN: Send the received evaluation to [@agent:Call Decision] to determine if the call quality is sufficient.\n5. Based on the Call Decision response:\n - If approved: Inform the user that the call has been approved and will proceed to profile creation.\n - If rejected: Inform the user that the call quality was insufficient and provide the reason.\n6. Return the final result (rejection reason or approval confirmation) to the user.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the sequential evaluation and decision process for interview transcripts.\n\n❌ Out of Scope:\n- Directly evaluating or creating profiles.\n- Handling transcripts not in the specified format.\n- Interacting with the individual evaluation agents.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Follow the strict sequence: Evaluation Agent first, then Call Decision.\n- Wait for each agent's complete response before proceeding.\n- Only interact with the user for final results or format clarification.\n\n🚫 Don'ts:\n- Do not perform evaluation or profile creation yourself.\n- Do not modify the transcript.\n- Do not try to get evaluations simultaneously.\n- Do not reference the individual evaluation agents.\n- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.\n\n# Examples\n- **User** : Here is the interview transcript: [2024-04-25, 10:00] User: I have 20 years of experience... [2024-04-25, 10:01] Assistant: Can you describe your leadership style?\n - **Agent actions**: \n 1. First call [@agent:Evaluation Agent](#mention)\n 2. Wait for complete evaluation\n 3. Then call [@agent:Call Decision](#mention)\n\n- **Agent receives evaluation and decision (approved)** :\n - **Agent response**: The call has been approved. Proceeding to candidate profile creation.\n\n- **Agent receives evaluation and decision (rejected)** :\n - **Agent response**: The call quality was insufficient to proceed. [Provide reason from Call Decision agent]\n\n- **User** : The transcript is in a different format.\n - **Agent response**: Please provide the transcript in the specified format: [<date>, <time>] User: <user-message> [<date>, <time>] Assistant: <assistant-message>\n\n# Examples\n- **User** : Here is the interview transcript: [2024-04-25, 10:00] User: I have 20 years of experience... [2024-04-25, 10:01] Assistant: Can you describe your leadership style?\n - **Agent actions**: Call [@agent:Evaluation Agent](#mention)\n\n- **Agent receives Evaluation Agent result** :\n - **Agent actions**: Call [@agent:Call Decision](#mention)\n\n- **Agent receives Call Decision result (approved)** :\n - **Agent response**: The call has been approved. Proceeding to candidate profile creation.\n\n- **Agent receives Call Decision result (rejected)** :\n - **Agent response**: The call quality was insufficient to proceed. [Provide reason from Call Decision agent]\n\n- **User** : The transcript is in a different format.\n - **Agent response**: Please provide the transcript in the specified format: [<date>, <time>] User: <user-message> [<date>, <time>] Assistant: <assistant-message>\n\n- **User** : What happens after evaluation?\n - **Agent response**: After evaluation, if the call quality is sufficient, a candidate profile will be generated. Otherwise, you will receive feedback on why the call was rejected.",
"model": "gpt-4o-mini", "model": "gpt-4o",
"hasRagSources": false,
"controlType": "retain", "controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"outputVisibility": "user_facing",
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [ "connectedAgents": [
"Order Issue", "Evaluation Agent",
"Delayed Delivery", "Call Decision"
"Escalation"
] ]
}, },
{ {
"name": "Post process", "name": "Exec Search Evaluation",
"type": "post_process", "type": "conversation",
"description": "", "description": "Evaluates the relevance and quality of questions asked by the executive search agency in the transcript.",
"instructions": "Ensure that the agent response is terse and to the point.", "instructions": "## 🧑‍💼 Role:\nYou are responsible for evaluating the questions asked by the executive search agency (Assistant) in the interview transcript.\n\n---\n## ⚙️ Steps to Follow:\n1. Review all 'Assistant' messages in the transcript.\n2. Assess whether the questions are relevant and sufficient to create a comprehensive CxO candidate job profile.\n3. Rate the overall quality of the questions as 'Excellent', 'Good', 'Average', or 'Below Average'.\n4. Provide a brief justification for your rating.\n5. Return your evaluation and justification.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Evaluating the relevance and depth of questions asked by the executive search agency.\n\n❌ Out of Scope:\n- Evaluating candidate responses.\n- Creating candidate profiles.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Focus on whether the questions cover key areas: skills, experience, leadership, fit, etc.\n- Be objective and concise in your justification.\n\n🚫 Don'ts:\n- Do not evaluate candidate answers.\n- Do not suggest improvements unless specifically asked.\n\n# Examples\n- **User** : [2024-04-25, 10:01] Assistant: Can you describe your leadership style? [2024-04-25, 10:02] Assistant: What are your key achievements as a CTO?\n - **Agent response**: Rating: Good. The questions are relevant and cover important aspects of the candidate's experience and leadership.\n\n- **User** : [2024-04-25, 10:01] Assistant: Tell me about yourself. [2024-04-25, 10:02] Assistant: What do you like to do in your free time?\n - **Agent response**: Rating: Below Average. The questions are too generic and do not sufficiently address the candidate's professional qualifications.\n\n- **User** : [2024-04-25, 10:01] Assistant: What is your favorite color?\n - **Agent response**: Rating: Below Average. The question is irrelevant for creating a CxO candidate profile.\n\n- **User** : [2024-04-25, 10:01] Assistant: Can you walk me through your experience leading large teams?\n - **Agent response**: Rating: Excellent. The question is highly relevant for assessing leadership skills.\n\n- **User** : [2024-04-25, 10:01] Assistant: How do you handle stress at work?\n - **Agent response**: Rating: Good. The question is relevant for understanding the candidate's coping mechanisms in high-pressure environments.",
"model": "gpt-4o-mini", "model": "gpt-4o",
"hasRagSources": false,
"controlType": "retain", "controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"outputVisibility": "internal",
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": []
}, },
{ {
"name": "Escalation", "name": "Candidate Evaluation",
"type": "escalation", "type": "conversation",
"description": "", "description": "Evaluates the quality, relevance, and clarity of the candidate's (User) responses in the transcript.",
"instructions": "Get the user's contact information and let them know that their request has been escalated.", "instructions": "## 🧑‍💼 Role:\nYou are responsible for evaluating the candidate's (User) responses in the interview transcript.\n\n---\n## ⚙️ Steps to Follow:\n1. Review all 'User' messages in the transcript.\n2. Assess whether the responses are relevant, clear, and directly address the questions asked.\n3. Identify if any responses are meandering, off-topic, or spam.\n4. Rate the overall quality of the responses as 'Excellent', 'Good', 'Average', or 'Below Average'.\n5. Provide a brief justification for your rating.\n6. Return your evaluation and justification.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Evaluating the quality and relevance of candidate responses.\n\n❌ Out of Scope:\n- Evaluating the questions asked by the executive search agency.\n- Creating candidate profiles.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Focus on clarity, relevance, and completeness of responses.\n- Be objective and concise in your justification.\n\n🚫 Don'ts:\n- Do not evaluate the questions asked.\n- Do not suggest improvements unless specifically asked.\n\n# Examples\n- **User** : [2024-04-25, 10:01] User: I have led teams of 100+ engineers and delivered multiple large-scale projects on time.\n - **Agent response**: Rating: Excellent. The response is clear, relevant, and directly addresses the question.\n\n- **User** : [2024-04-25, 10:01] User: Well, you know, it's like, sometimes things just happen, and I just do my best, you know?\n - **Agent response**: Rating: Below Average. The response is vague and does not provide meaningful information.\n\n- **User** : [2024-04-25, 10:01] User: I have experience in many things, but let's talk about something else.\n - **Agent response**: Rating: Below Average. The response is off-topic and does not address the question.\n\n- **User** : [2024-04-25, 10:01] User: I have successfully managed cross-functional teams and implemented new processes that improved efficiency by 20%.\n - **Agent response**: Rating: Good. The response is relevant and provides specific achievements.\n\n- **User** : [2024-04-25, 10:01] User: I am a hard worker.\n - **Agent response**: Rating: Average. The response is generic and lacks detail.",
"model": "gpt-4o-mini", "model": "gpt-4o",
"hasRagSources": false,
"controlType": "retain", "controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"outputVisibility": "internal",
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": []
}, },
{ {
"name": "Order Issue", "name": "Call Decision",
"type": "conversation", "type": "conversation",
"description": "Agent to assist users with missing or incorrect order items.", "description": "Decides whether the call quality is sufficient to proceed to candidate profile creation based on the evaluations.",
"instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nAssist users with issues related to missing or incorrect order items.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the issue with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Handling missing or incorrect order items\n\n\u274c Out of Scope:\n- Delayed delivery issues\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Ensure the user is aware of the order details before proceeding.\n\n\ud83d\udeab Don'ts:\n- Extend the conversation beyond order issues.\n\n# Examples\n- **User** : I received the wrong item in my order.\n - **Agent response**: I can help with that. Let me fetch your order details first.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing an item.\n - **Agent response**: Let's check your order details and resolve this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I got someone else's order.\n - **Agent response**: I apologize for the mix-up. I'll fetch your order details to sort this out.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : Can you help me with a missing item?\n - **Agent response**: Certainly, I'll look into your order details right away.\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : There's an issue with my order items.\n - **Agent response**: Let's verify your order details to address this issue.\n - **Agent actions**: Call [@tool:get_order_details](#mention)", "instructions": "## 🧑‍💼 Role:\nYou are responsible for making a decision based on the evaluations from the Exec Search and Candidate Evaluation agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the ratings and justifications from both evaluation agents.\n2. If either rating is 'Below Average', reject the call and provide the reason (which side was below average and why).\n3. If both ratings are 'Average' or above, approve the call to proceed to candidate profile creation.\n4. Return your decision and, if rejected, the reason for rejection.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Deciding whether to proceed to profile creation or reject the call.\n\n❌ Out of Scope:\n- Evaluating the transcript directly.\n- Creating candidate profiles.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Be clear and concise in your decision.\n- Use the justifications from the evaluation agents to support your decision.\n\n🚫 Don'ts:\n- Do not override the evaluation agents' ratings.\n\n# Examples\n- **User** : Exec Search: Good. Candidate: Excellent.\n - **Agent response**: Decision: Approved. Both sides performed well. Proceed to candidate profile creation.\n\n- **User** : Exec Search: Below Average. Candidate: Good.\n - **Agent response**: Decision: Rejected. The executive search agency's questions were below average: [reason].\n\n- **User** : Exec Search: Good. Candidate: Below Average.\n - **Agent response**: Decision: Rejected. The candidate's responses were below average: [reason].\n\n- **User** : Exec Search: Average. Candidate: Average.\n - **Agent response**: Decision: Approved. Both sides performed adequately. Proceed to candidate profile creation.\n\n- **User** : Exec Search: Excellent. Candidate: Excellent.\n - **Agent response**: Decision: Approved. Both sides performed excellently. Proceed to candidate profile creation.",
"model": "gpt-4o", "model": "gpt-4o",
"hasRagSources": false,
"controlType": "retain", "controlType": "retain",
"tools": [ "ragK": 3,
"get_order_details" "ragReturnType": "chunks",
], "outputVisibility": "internal",
"tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": []
}, },
{ {
"name": "Delayed Delivery", "name": "Evaluation Agent",
"type": "conversation", "type": "conversation",
"description": "Agent to assist users with delayed delivery issues.", "description": "Coordinates the evaluation of the interview transcript by both the Exec Search Evaluation and Candidate Evaluation agents.",
"instructions": "## \ud83e\uddd1\u200d\ud83d\udcbc Role:\nAssist users with issues related to delayed delivery.\n\n---\n## \u2699\ufe0f Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Confirm the delay with the user.\n3. Provide solutions or escalate if unresolved.\n\n---\n## \ud83c\udfaf Scope:\n\u2705 In Scope:\n- Handling delayed delivery issues\n\n\u274c Out of Scope:\n- Missing or incorrect order items\n- General knowledge queries\n\n---\n## \ud83d\udccb Guidelines:\n\u2714\ufe0f Dos:\n- Ensure the user is aware of the delivery status before proceeding.\n\n\ud83d\udeab Don'ts:\n- Extend the conversation beyond delivery issues.\n\n# Examples\n- **User** : My delivery is late.\n - **Agent response**: I can help with that. Let me fetch your delivery status first.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Where is my order? It's delayed.\n - **Agent response**: Let's check your delivery status and resolve this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : My order hasn't arrived yet.\n - **Agent response**: I apologize for the delay. I'll fetch your delivery status to sort this out.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Can you help me with a delayed delivery?\n - **Agent response**: Certainly, I'll look into your delivery status right away.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : There's an issue with my delivery timing.\n - **Agent response**: Let's verify your delivery status to address this issue.\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)", "instructions": "## 🧑‍💼 Role:\nYou are responsible for coordinating the evaluation of the interview transcript by both the Exec Search Evaluation and Candidate Evaluation agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the transcript from the hub agent.\n2. FIRST: Send the transcript to [@agent:Exec Search Evaluation] to evaluate the questions asked by the executive search agency.\n3. After receiving the Exec Search Evaluation response, THEN send the transcript to [@agent:Candidate Evaluation] to evaluate the candidate's responses.\n4. Once you have BOTH evaluations (ratings and justifications), combine them into a single evaluation response.\n5. Return the combined evaluation to the hub agent.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Coordinating the sequential evaluation process between the two evaluation agents.\n\n❌ Out of Scope:\n- Making decisions about call quality.\n- Creating candidate profiles.\n- Interacting directly with the user.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Follow the strict sequence: Exec Search first, then Candidate.\n- Wait for each evaluation to complete before proceeding.\n- Combine both evaluations into a single response.\n\n🚫 Don'ts:\n- Do not evaluate the transcript yourself.\n- Do not try to get both evaluations simultaneously.\n- Do not interact with the user.\n- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.\n\n# Examples\n- **User** : [2024-04-25, 10:01] User: I have 20 years of experience... [2024-04-25, 10:01] Assistant: Can you describe your leadership style?\n - **Agent actions**: \n 1. First call [@agent:Exec Search Evaluation](#mention)\n 2. After receiving response, then call [@agent:Candidate Evaluation](#mention)\n 3. Combine both evaluations into single response\n\n- **Agent receives both evaluations** :\n - **Agent response**: Returns combined evaluations (ratings and justifications) to the hub agent.\n\n# Examples\n- **User** : [2024-04-25, 10:01] User: I have 20 years of experience... [2024-04-25, 10:01] Assistant: Can you describe your leadership style?\n - **Agent actions**: Call [@agent:Exec Search Evaluation](#mention), Call [@agent:Candidate Evaluation](#mention)\n\n- **Agent receives both evaluations** :\n - **Agent response**: Returns both evaluations (ratings and justifications) to the hub agent.",
"model": "gpt-4o", "model": "gpt-4o",
"hasRagSources": false,
"controlType": "retain", "controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"outputVisibility": "internal",
"tools": [],
"prompts": [],
"connectedAgents": [
"Exec Search Evaluation",
"Candidate Evaluation"
]
}
],
"tools": [ "tools": [
"get_delivery_status" {
"name": "web_search",
"description": "Fetch information from the web based on chat context",
"parameters": {
"type": "object",
"properties": {}
},
"isLibrary": true
}
], ],
"prompts": [], "prompts": [],
"connectedAgents": [] "startAgent": "Interview Evaluation Hub",
} "mcpServers": [],
], "toolWebhookUrl": ""
"tools": [
{
"name": "get_order_details",
"description": "Tool to fetch details about the user's order.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The unique identifier for the order."
}
}, },
"required": [ "lastResponse": null
"order_id"
]
},
"mockTool": true,
"mockInstructions": "Return a mock response for Door Dash order details."
},
{
"name": "get_delivery_status",
"description": "Tool to fetch the current status of the delivery.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The unique identifier for the order."
}
},
"required": [
"order_id"
]
}
}
],
"prompts": [
{
"name": "Style prompt",
"type": "style_prompt",
"prompt": "You should be empathetic and helpful."
}
],
"startAgent": "Door Dash Hub"
}
} }

View file

@ -1,6 +1,98 @@
{ {
"messages": [
{
"role": "system",
"content": ""
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T07:17:21.052Z",
"role": "assistant",
"content": "How can I help you today?",
"agenticSender": "Credit Card Hub",
"agenticResponseType": "external"
},
{
"role": "user",
"content": "What is the process for applying for a card?",
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T07:17:31.404Z"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T07:17:32.671Z",
"role": "assistant",
"tool_calls": [
{
"id": "404dafe5-7b5b-4617-bd2f-ecac3119c928",
"function": {
"name": "transfer_to_agent",
"arguments": "{\"assistant\": \"Card Application Process\"}"
},
"type": "function"
}
],
"agenticSender": "Credit Card Hub",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T07:17:32.671Z",
"role": "tool",
"content": "{\"assistant\": \"Card Application Process\"}",
"tool_call_id": "404dafe5-7b5b-4617-bd2f-ecac3119c928",
"tool_name": "transfer_to_agent"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T07:17:33.734Z",
"role": "assistant",
"content": "Which credit card are you interested in applying for?",
"agenticSender": "Card Application Process",
"agenticResponseType": "external"
},
{
"role": "user",
"content": "Amex centurion",
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T07:17:41.963Z"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T07:17:55.790Z",
"role": "assistant",
"tool_calls": [
{
"id": "ws_6819b79705a08192a819334a0c7900b6004e105788fca541",
"function": {
"name": "web_search",
"arguments": "{\"search_id\": \"ws_6819b79705a08192a819334a0c7900b6004e105788fca541\"}"
},
"type": "function"
}
],
"agenticSender": "Card Application Process",
"agenticResponseType": "internal"
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-06T07:17:55.790Z",
"role": "assistant",
"content": "The American Express Centurion Card, commonly known as the \"Black Card,\" is an invitation-only charge card designed for high-net-worth individuals. To be considered for an invitation, you typically need to meet the following criteria:\n\n- **Existing American Express Relationship**: You should have an active American Express card account.\n\n- **High Annual Spending**: While American Express does not publicly disclose specific spending thresholds, reports suggest that spending between $250,000 and $500,000 annually on your American Express cards may increase your chances of receiving an invitation. ([nerdwallet.com](https://www.nerdwallet.com/article/travel/centurion-black-card?utm_source=openai))\n\n- **Luxury Spending Patterns**: Expenditures on luxury items, such as high-end travel, fine dining, and premium goods, are considered favorable. ([fool.com](https://www.fool.com/the-ascent/credit-cards/articles/your-guide-to-getting-an-amex-black-card/?utm_source=openai))\n\n- **Excellent Credit Score**: A credit score of 800 or higher is typically expected. ([finance.yahoo.com](https://finance.yahoo.com/personal-finance/american-express-centurion-card-213619679.html?utm_source=openai))\n\nIf you believe you meet these criteria, you can express your interest by visiting the official Centurion website and submitting a request for consideration. Please note that submitting a request does not guarantee an invitation, as American Express evaluates each request based on its internal criteria. ([thepointsguy.com](https://thepointsguy.com/news/you-can-now-request-an-invite-to-the-amex-centurion-card/?utm_source=openai))\n\nShould you receive an invitation and choose to accept it, be prepared for the following fees:\n\n- **Initiation Fee**: A one-time fee of $10,000.\n\n- **Annual Fee**: An ongoing annual fee of $5,000. ([forbes.com](https://www.forbes.com/advisor/credit-cards/reviews/centurion-from-american-express/?utm_source=openai))\n\nPlease be aware that the Centurion Card is designed for individuals with substantial financial resources and specific spending habits. If you do not meet these criteria, American Express offers a range of other premium cards that might better suit your needs. ",
"agenticSender": "Card Application Process",
"agenticResponseType": "external"
}
],
"lastRequest": { "lastRequest": {
"projectId": "4ebd5e81-010a-4bc6-91e0-0aa98173dbac", "projectId": "05d64a45-8468-4783-b5df-c9644d2d7ba5",
"messages": [ "messages": [
{ {
"content": "", "content": "",
@ -11,25 +103,59 @@
"tool_name": null "tool_name": null
}, },
{ {
"content": "Hello! How can I assist you with your DoorDash query today? Are you inquiring about a missing item, delivery status, or subscription details?", "content": "How can I help you today?",
"role": "assistant", "role": "assistant",
"sender": "DoorDash Support Hub", "sender": "Credit Card Hub",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "internal"
},
{
"content": "Hello! How can I assist you with your DoorDash query today? Are you inquiring about a missing item, delivery status, or subscription details?",
"role": "assistant",
"sender": "DoorDash Support Hub >> External",
"tool_calls": null, "tool_calls": null,
"tool_call_id": null, "tool_call_id": null,
"tool_name": null, "tool_name": null,
"response_type": "external" "response_type": "external"
}, },
{ {
"content": "hi", "content": "What is the process for applying for a card?",
"role": "user",
"sender": null,
"tool_calls": null,
"tool_call_id": null,
"tool_name": null
},
{
"content": null,
"role": "assistant",
"sender": "Credit Card Hub",
"tool_calls": [
{
"id": "404dafe5-7b5b-4617-bd2f-ecac3119c928",
"function": {
"name": "transfer_to_agent",
"arguments": "{\"assistant\": \"Card Application Process\"}"
},
"type": "function"
}
],
"tool_call_id": null,
"tool_name": null,
"response_type": "internal"
},
{
"content": "{\"assistant\": \"Card Application Process\"}",
"role": "tool",
"sender": null,
"tool_calls": null,
"tool_call_id": "404dafe5-7b5b-4617-bd2f-ecac3119c928",
"tool_name": "transfer_to_agent"
},
{
"content": "Which credit card are you interested in applying for?",
"role": "assistant",
"sender": "Card Application Process",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "external"
},
{
"content": "Amex centurion",
"role": "user", "role": "user",
"sender": null, "sender": null,
"tool_calls": null, "tool_calls": null,
@ -38,40 +164,30 @@
} }
], ],
"state": { "state": {
"agent_data": [ "last_agent_name": "Card Application Process",
{ "tokens": {
"instructions": "## 🧑‍💼 Role:\nYou are responsible for directing DoorDash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which DoorDash-related query they need help with (e.g., 'Are you inquiring about a missing item, delivery status, or subscription details?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Missing Items → [@agent:Missing Items]\n - Delivery Status → [@agent:Delivery Status]\n - Subscription Info → [@agent:Subscription Info]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Missing items queries\n- Delivery status queries\n- Subscription-related queries\n\n❌ Out of Scope:\n- Issues unrelated to DoorDash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Direct queries to specific DoorDash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond DoorDash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with a missing item in my order.\n - **Agent actions**: Call [@agent:Missing Items](#mention)\n\n- **User** : Can you tell me the status of my delivery?\n - **Agent actions**: Call [@agent:Delivery Status](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would you like help with today?\n\n- **User** : Can you reset my account settings?\n - **Agent actions**: Call [@agent:Escalation](#mention)\n\n- **User** : I have a question about my order.\n - **Agent response**: Could you specify if it's about a missing item, delivery status, or subscription details?\n\n- **User** : What are the available subscription plans?\n - **Agent actions**: Call [@agent:Subscription Info](#mention)", "total": 0,
"name": "DoorDash Support Hub" "prompt": 0,
"completion": 0
}, },
"turn_messages": [
{ {
"instructions": "Ensure that the agent response is terse and to the point.", "content": "Sender agent: Card Application Process\nContent: Which credit card are you interested in applying for?",
"name": "Post process" "role": "assistant",
}, "sender": "Card Application Process",
{ "tool_calls": null,
"instructions": "Get the user's contact information and let them know that their request has been escalated.", "tool_call_id": null,
"name": "Escalation" "tool_name": null,
}, "response_type": "external"
{
"instructions": "## 🧑‍💼 Role:\nHelp users resolve issues with missing items in their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the missing items with the user.\n3. Provide resolution options or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Resolving missing items issues\n\n❌ Out of Scope:\n- Delivery status queries\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use [@tool:get_order_details](#mention) to fetch accurate order information.\n- Provide clear resolution options.\n\n🚫 Don'ts:\n- Assume missing items without user confirmation.\n- Extend the conversation beyond missing items.\n\n# Examples\n- **User** : I didn't receive my fries with my order.\n - **Agent response**: Let me check your order details. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing a drink.\n - **Agent response**: I apologize for the inconvenience. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get the extra sauce I ordered.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My burger is missing from the order.\n - **Agent response**: I'm sorry to hear that. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get my dessert.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
"name": "Missing Items"
},
{
"instructions": "## 🧑‍💼 Role:\nHelp users with queries related to the delivery status of their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Provide the user with the current delivery status.\n3. Offer additional assistance if needed.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Providing delivery status updates\n\n❌ Out of Scope:\n- Resolving missing items issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use [@tool:get_delivery_status](#mention) to fetch accurate delivery information.\n- Provide clear and concise status updates.\n\n🚫 Don'ts:\n- Provide status updates without fetching current information.\n- Extend the conversation beyond delivery status.\n\n# Examples\n- **User** : Where is my order?\n - **Agent response**: Let me check the delivery status for you. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order on the way?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : When will my order arrive?\n - **Agent response**: Let me find out the delivery status. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : How long until my order gets here?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order delayed?\n - **Agent response**: Let me check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
"name": "Delivery Status"
},
{
"instructions": "## 🧑‍💼 Role:\nProvide information and answer questions related to subscriptions using RAG.\n\n---\n## ⚙️ Steps to Follow:\n1. Use RAG to retrieve relevant information about subscriptions.\n2. Answer the user's questions based on the retrieved information.\n3. If the user's query is outside the scope of subscriptions, inform them politely.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Subscription plans and pricing\n- Subscription features and benefits\n\n❌ Out of Scope:\n- Non-subscription-related queries\n- Detailed account management\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use RAG to provide accurate and up-to-date information.\n- Be clear and concise in your responses.\n\n🚫 Don'ts:\n- Provide information without using RAG.\n- Extend the conversation beyond subscription topics.\n\n# Examples\n- **User** : What are the available subscription plans?\n - **Agent response**: Let me check the available subscription plans for you.\n\n- **User** : How much does the premium subscription cost?\n - **Agent response**: I'll find the current pricing for the premium subscription.\n\n- **User** : Can I change my subscription plan?\n - **Agent response**: I'll provide information on how to change your subscription plan.\n\n- **User** : What benefits do I get with a subscription?\n - **Agent response**: Let me retrieve the benefits associated with our subscription plans.\n\n- **User** : Is there a free trial available?\n - **Agent response**: I'll check if there's a free trial available for our subscriptions.",
"name": "Subscription Info"
} }
], ]
"last_agent_name": "DoorDash Support Hub"
}, },
"agents": [ "agents": [
{ {
"name": "DoorDash Support Hub", "name": "Credit Card Hub",
"type": "conversation", "type": "conversation",
"description": "Hub agent to manage DoorDash-related queries.", "description": "Hub agent to route credit card related queries to the appropriate sub-agent.",
"instructions": "## 🧑‍💼 Role:\nYou are responsible for directing DoorDash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which DoorDash-related query they need help with (e.g., 'Are you inquiring about a missing item, delivery status, or subscription details?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Missing Items → [@agent:Missing Items]\n - Delivery Status → [@agent:Delivery Status]\n - Subscription Info → [@agent:Subscription Info]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Missing items queries\n- Delivery status queries\n- Subscription-related queries\n\n❌ Out of Scope:\n- Issues unrelated to DoorDash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Direct queries to specific DoorDash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond DoorDash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with a missing item in my order.\n - **Agent actions**: Call [@agent:Missing Items](#mention)\n\n- **User** : Can you tell me the status of my delivery?\n - **Agent actions**: Call [@agent:Delivery Status](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would you like help with today?\n\n- **User** : Can you reset my account settings?\n - **Agent actions**: Call [@agent:Escalation](#mention)\n\n- **User** : I have a question about my order.\n - **Agent response**: Could you specify if it's about a missing item, delivery status, or subscription details?\n\n- **User** : What are the available subscription plans?\n - **Agent actions**: Call [@agent:Subscription Info](#mention)", "instructions": "## 🧑‍💼 Role:\nYou are the hub for all credit card related queries. Your job is to understand the user's intent and route them to the correct agent: recommendations, benefits, or application process.\n\n---\n## ⚙️ Steps to Follow:\n1. If the user wants card recommendations, call [@agent:Card Recommendation].\n2. If the user asks about card benefits or features, call [@agent:Card Benefits].\n3. If the user wants to know about the application process, call [@agent:Card Application Process].\n4. If the query is unclear, ask a clarifying question.\n5. If the query is out of scope, politely inform the user.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Routing queries about credit card recommendations, benefits, and application process.\n\n❌ Out of Scope:\n- Non-credit card related queries.\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Be professional and friendly.\n- Ask clarifying questions if unsure.\n\n🚫 Don'ts:\n- Do not answer detailed questions yourself; always route to the correct agent.\n- Do not say 'connecting you to an agent'.\n\n\n# Examples\n- **User**: I want to know which credit card is best for travel.\n - **Agent actions**: Call [@agent:Card Recommendation](#mention)\n\n- **User**: What are the benefits of your platinum card?\n - **Agent actions**: Call [@agent:Card Benefits](#mention)\n\n- **User**: How do I apply for a credit card?\n - **Agent actions**: Call [@agent:Card Application Process](#mention)\n\n- **User**: Can you help me with my savings account?\n - **Agent response**: I'm sorry, I can only assist with credit card related queries.\n\n- **User**: Hi, I have a question.\n - **Agent response**: Hello! How can I help you with credit cards today?",
"model": "gpt-4o", "model": "gpt-4o",
"controlType": "retain", "controlType": "retain",
"ragK": 3, "ragK": 3,
@ -79,192 +195,191 @@
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [ "connectedAgents": [
"Missing Items", "Card Recommendation",
"Delivery Status", "Card Benefits",
"Subscription Info", "Card Application Process"
"Escalation"
] ]
}, },
{ {
"name": "Post process", "name": "Card Recommendation",
"type": "post_process",
"description": "",
"instructions": "Ensure that the agent response is terse and to the point.",
"model": "gpt-4o-mini",
"controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"tools": [],
"prompts": [],
"connectedAgents": []
},
{
"name": "Escalation",
"type": "escalation",
"description": "",
"instructions": "Get the user's contact information and let them know that their request has been escalated.",
"model": "gpt-4o-mini",
"controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"tools": [],
"prompts": [],
"connectedAgents": []
},
{
"name": "Missing Items",
"type": "conversation", "type": "conversation",
"description": "Agent to assist users with missing items in their orders.", "description": "Recommends suitable credit cards based on user preferences and needs.",
"instructions": "## 🧑‍💼 Role:\nHelp users resolve issues with missing items in their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the missing items with the user.\n3. Provide resolution options or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Resolving missing items issues\n\n❌ Out of Scope:\n- Delivery status queries\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use [@tool:get_order_details](#mention) to fetch accurate order information.\n- Provide clear resolution options.\n\n🚫 Don'ts:\n- Assume missing items without user confirmation.\n- Extend the conversation beyond missing items.\n\n# Examples\n- **User** : I didn't receive my fries with my order.\n - **Agent response**: Let me check your order details. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing a drink.\n - **Agent response**: I apologize for the inconvenience. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get the extra sauce I ordered.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My burger is missing from the order.\n - **Agent response**: I'm sorry to hear that. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get my dessert.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)", "instructions": "## 🧑‍💼 Role:\nHelp users find the best credit card for their needs by asking about their preferences and providing tailored recommendations.\n\n---\n## ⚙️ Steps to Follow:\n1. Ask the user about their preferences (e.g., travel, cashback, low interest, rewards). If they have already mentioned one of the categories, skip to the next step directly. \n2. Use [@tool:recommendation] to find suitable credit card matching their needs.\n3. If the request is out of scope, call [@agent:Credit Card Hub] \n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Recommending credit cards based on user needs.\n- Explaining why a card is recommended.\n\n❌ Out of Scope:\n- Detailed card benefits (refer to Card Benefits agent).\n- Application process (refer to Card Application Process agent).\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Be professional and friendly.\n- Tailor recommendations to user input.\n\n🚫 Don'ts:\n- Recommend cards without understanding user needs.\n\n\n\n\n# Examples\n- **User**: I want a card with good travel rewards.\n - **Agent actions**: Call [@tool:web_search]\n - **Agent response**: Based on your interest in travel rewards, here are some great options: 1. Travel Platinum Card excellent for frequent flyers, 2. Explorer Rewards Card great for international travel. Would you like more details on any of these?\n\n- **User**: Which card is best for cashback?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: For cashback, I recommend: 1. Cashback Plus Card up to 5% cashback on groceries, 2. Everyday Saver Card 2% cashback on all purchases. Would you like to know more?\n\n- **User**: I want a card with a low interest rate.\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: Here are some low interest rate cards: 1. Low Rate Advantage Card, 2. Simple Interest Card. Let me know if you want more information.\n\n- **User**: Can you recommend a card for students?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: For students, these cards are popular: 1. Student Starter Card, 2. Young Achiever Card. Would you like details on their features?\n\n- **User**: What if I want both travel and cashback?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: There are hybrid cards that offer both travel rewards and cashback. Here are two options: 1. Flexi Rewards Card, 2. Global Cashback Card. Would you like to compare them?",
"model": "gpt-4o", "model": "gpt-4o",
"controlType": "retain", "controlType": "retain",
"ragK": 3, "ragK": 3,
"ragReturnType": "chunks", "ragReturnType": "chunks",
"outputVisibility": "user_facing",
"tools": [ "tools": [
"get_order_details" "recommendation",
"web_search"
], ],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": [
"Credit Card Hub"
]
}, },
{ {
"name": "Delivery Status", "name": "Card Benefits",
"type": "conversation", "type": "conversation",
"description": "Agent to assist users with delivery status queries.", "description": "Provides detailed information about the benefits and features of specific credit cards.",
"instructions": "## 🧑‍💼 Role:\nHelp users with queries related to the delivery status of their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Provide the user with the current delivery status.\n3. Offer additional assistance if needed.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Providing delivery status updates\n\n❌ Out of Scope:\n- Resolving missing items issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use [@tool:get_delivery_status](#mention) to fetch accurate delivery information.\n- Provide clear and concise status updates.\n\n🚫 Don'ts:\n- Provide status updates without fetching current information.\n- Extend the conversation beyond delivery status.\n\n# Examples\n- **User** : Where is my order?\n - **Agent response**: Let me check the delivery status for you. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order on the way?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : When will my order arrive?\n - **Agent response**: Let me find out the delivery status. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : How long until my order gets here?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order delayed?\n - **Agent response**: Let me check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)", "instructions": "## 🧑‍💼 Role:\nAnswer user questions about the benefits, features, and perks of credit cards.\n\n---\n## ⚙️ Steps to Follow:\n1. Ask the user which card they want to know about if not specified.\n2. Use [@tool:web_search] to find up-to-date information about the card's benefits.\n3. Summarize the key benefits and features in a clear, friendly, and professional manner.\n4. Offer to answer follow-up questions or compare with other cards.\n5. If the request is out of scope, call [@agent:Credit Card Hub].\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Explaining card benefits, features, and perks.\n- Comparing benefits between cards.\n\n❌ Out of Scope:\n- Recommending cards (refer to Card Recommendation agent).\n- Application process (refer to Card Application Process agent).\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Be accurate, clear, and concise.\n- Use up-to-date information.\n\n🚫 Don'ts:\n- Speculate about benefits; always check with web_search.\n\n# Examples\n- **User**: What are the benefits of the Platinum Card?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: The Platinum Card offers benefits such as airport lounge access, travel insurance, and high reward points on travel spending. Would you like more details on any of these?\n\n- **User**: Does the Cashback Plus Card have purchase protection?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: Yes, the Cashback Plus Card includes purchase protection for eligible items. Would you like to know the coverage details?\n\n- **User**: Can you compare the benefits of the Student Starter Card and the Young Achiever Card?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: Heres a comparison: Student Starter Card no annual fee, basic rewards; Young Achiever Card higher rewards, some travel perks. Would you like a detailed breakdown?\n\n- **User**: What perks come with the Explorer Rewards Card?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: The Explorer Rewards Card offers perks like complimentary travel insurance, bonus points on international purchases, and concierge service.\n\n- **User**: Tell me about the insurance benefits of the Low Rate Advantage Card.\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: The Low Rate Advantage Card includes travel accident insurance and extended warranty on purchases. Would you like more information?",
"model": "gpt-4o", "model": "gpt-4o",
"controlType": "retain", "controlType": "retain",
"ragK": 3, "ragK": 3,
"ragReturnType": "chunks", "ragReturnType": "chunks",
"outputVisibility": "user_facing",
"tools": [ "tools": [
"get_delivery_status" "web_search"
], ],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": [
"Credit Card Hub"
]
}, },
{ {
"name": "Subscription Info", "name": "Card Application Process",
"type": "conversation", "type": "conversation",
"description": "Agent to assist users with subscription-related queries.", "description": "Guides users through the credit card application process, including eligibility and required documents.",
"instructions": "## 🧑‍💼 Role:\nProvide information and answer questions related to subscriptions using RAG.\n\n---\n## ⚙️ Steps to Follow:\n1. Use RAG to retrieve relevant information about subscriptions.\n2. Answer the user's questions based on the retrieved information.\n3. If the user's query is outside the scope of subscriptions, inform them politely.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Subscription plans and pricing\n- Subscription features and benefits\n\n❌ Out of Scope:\n- Non-subscription-related queries\n- Detailed account management\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use RAG to provide accurate and up-to-date information.\n- Be clear and concise in your responses.\n\n🚫 Don'ts:\n- Provide information without using RAG.\n- Extend the conversation beyond subscription topics.\n\n# Examples\n- **User** : What are the available subscription plans?\n - **Agent response**: Let me check the available subscription plans for you.\n\n- **User** : How much does the premium subscription cost?\n - **Agent response**: I'll find the current pricing for the premium subscription.\n\n- **User** : Can I change my subscription plan?\n - **Agent response**: I'll provide information on how to change your subscription plan.\n\n- **User** : What benefits do I get with a subscription?\n - **Agent response**: Let me retrieve the benefits associated with our subscription plans.\n\n- **User** : Is there a free trial available?\n - **Agent response**: I'll check if there's a free trial available for our subscriptions.", "instructions": "## 🧑‍💼 Role:\nGuide users through the process of applying for a credit card, including eligibility, required documents, and next steps.\n\n---\n## ⚙️ Steps to Follow:\n1. Ask the user which card they want to apply for - if they haven't already told you.\n2. Using [@tool:web_search], search the web for the needed information. You have to use the web_search tool without fail. You cannot make up information.\n3. If the request is out of scope, call [@agent:Credit Card Hub].\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Explaining application steps, eligibility, and required documents.\n- Guiding users through the process.\n\n❌ Out of Scope:\n- Recommending cards (refer to Card Recommendation agent).\n- Explaining card benefits (refer to Card Benefits agent).\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Be clear, step-by-step, and supportive.\n- Use up-to-date information.\n\n🚫 Don'ts:\n- Provide application links unless verified via web_search.\n\n\n# Examples\n- **User**: How do I apply for the Platinum Card?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: To apply for the Platinum Card, youll need to meet the eligibility criteria (e.g., minimum income, age) and provide documents like ID and proof of income. You can apply online or at a branch. Would you like a step-by-step guide?\n\n- **User**: What documents do I need to apply for a credit card?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: Typically, youll need a government-issued ID, proof of income, and address verification. Would you like details for a specific card?\n\n- **User**: Can I apply for a card as a student?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: Many banks offer student credit cards with relaxed eligibility. Youll usually need proof of enrollment and a student ID. Would you like to see some options?\n\n- **User**: Is there an online application for the Cashback Plus Card?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: Yes, you can apply online for the Cashback Plus Card. I can guide you through the steps or provide the official link if youd like.\n\n- **User**: Whats the minimum income for the Explorer Rewards Card?\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: The minimum income requirement for the Explorer Rewards Card is typically $30,000 per year. Would you like more details on eligibility?",
"model": "gpt-4o", "model": "gpt-4o",
"controlType": "relinquish_to_parent", "controlType": "retain",
"ragDataSources": [
"67e1612510540d9027909e10"
],
"ragK": 3, "ragK": 3,
"ragReturnType": "content", "ragReturnType": "chunks",
"tools": [], "outputVisibility": "user_facing",
"tools": [
"web_search"
],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": [
"Credit Card Hub"
]
} }
], ],
"tools": [ "tools": [
{ {
"name": "get_order_details", "name": "web_search",
"description": "Tool to fetch the user's order details.", "description": "Fetch information from the web based on chat context",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {}
"order_id": {
"type": "string",
"description": "The unique identifier for the order."
}
}, },
"required": [ "isLibrary": true
"order_id"
]
}
}, },
{ {
"name": "get_delivery_status", "name": "recommendation",
"description": "Tool to fetch the delivery status of an order.", "description": "Recommends a card",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {}
"order_id": {
"type": "string",
"description": "The unique identifier for the order."
}
},
"required": [
"order_id"
]
}, },
"mockTool": true, "mockTool": true,
"mockInstructions": "Give a mock response for a door dash order delivery status." "mockInstructions": "Recommend a random credit card"
} }
], ],
"prompts": [ "prompts": [],
{ "startAgent": "Credit Card Hub",
"name": "Style prompt", "mcpServers": [],
"type": "style_prompt", "toolWebhookUrl": ""
"prompt": "You should be empathetic and helpful."
},
{
"name": "Greeting",
"type": "greeting",
"prompt": "Hello! How can I help you?"
}
],
"startAgent": "DoorDash Support Hub",
"mcpServers": [
{
"name": "delivery",
"url": "http://localhost:8000/sse"
}
],
"toolWebhookUrl": "http://127.0.0.1:4020/tool_call"
}, },
"lastResponse": { "lastResponse": {
"state": {
"last_agent_name": "Card Application Process",
"tokens": {
"total": 0,
"prompt": 0,
"completion": 0
},
"turn_messages": [
{
"content": "Sender agent: Card Application Process\nContent: None",
"role": "assistant",
"sender": "Card Application Process",
"tool_calls": [
{
"function": {
"name": "web_search",
"arguments": "{\"search_id\": \"ws_6819b79705a08192a819334a0c7900b6004e105788fca541\"}"
},
"id": "ws_6819b79705a08192a819334a0c7900b6004e105788fca541",
"type": "function"
}
],
"tool_call_id": null,
"tool_name": null,
"response_type": "internal"
},
{
"content": "Sender agent: Card Application Process\nContent: The American Express Centurion Card, commonly known as the \"Black Card,\" is an invitation-only charge card designed for high-net-worth individuals. To be considered for an invitation, you typically need to meet the following criteria:\n\n- **Existing American Express Relationship**: You should have an active American Express card account.\n\n- **High Annual Spending**: While American Express does not publicly disclose specific spending thresholds, reports suggest that spending between $250,000 and $500,000 annually on your American Express cards may increase your chances of receiving an invitation. ([nerdwallet.com](https://www.nerdwallet.com/article/travel/centurion-black-card?utm_source=openai))\n\n- **Luxury Spending Patterns**: Expenditures on luxury items, such as high-end travel, fine dining, and premium goods, are considered favorable. ([fool.com](https://www.fool.com/the-ascent/credit-cards/articles/your-guide-to-getting-an-amex-black-card/?utm_source=openai))\n\n- **Excellent Credit Score**: A credit score of 800 or higher is typically expected. ([finance.yahoo.com](https://finance.yahoo.com/personal-finance/american-express-centurion-card-213619679.html?utm_source=openai))\n\nIf you believe you meet these criteria, you can express your interest by visiting the official Centurion website and submitting a request for consideration. Please note that submitting a request does not guarantee an invitation, as American Express evaluates each request based on its internal criteria. ([thepointsguy.com](https://thepointsguy.com/news/you-can-now-request-an-invite-to-the-amex-centurion-card/?utm_source=openai))\n\nShould you receive an invitation and choose to accept it, be prepared for the following fees:\n\n- **Initiation Fee**: A one-time fee of $10,000.\n\n- **Annual Fee**: An ongoing annual fee of $5,000. ([forbes.com](https://www.forbes.com/advisor/credit-cards/reviews/centurion-from-american-express/?utm_source=openai))\n\nPlease be aware that the Centurion Card is designed for individuals with substantial financial resources and specific spending habits. If you do not meet these criteria, American Express offers a range of other premium cards that might better suit your needs. ",
"role": "assistant",
"sender": "Card Application Process",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "external",
"citations": [
{
"url": "https://www.nerdwallet.com/article/travel/centurion-black-card?utm_source=openai",
"title": "10 Things to Know About the AmEx Centurion Card - NerdWallet",
"start_index": 602,
"end_index": 702
},
{
"url": "https://www.fool.com/the-ascent/credit-cards/articles/your-guide-to-getting-an-amex-black-card/?utm_source=openai",
"title": "Your Guide to Getting an Amex Black Card",
"start_index": 849,
"end_index": 976
},
{
"url": "https://finance.yahoo.com/personal-finance/american-express-centurion-card-213619679.html?utm_source=openai",
"title": "Are you rich enough to score an Amex Centurion Card? (Spoiler: You're not)",
"start_index": 1063,
"end_index": 1193
},
{
"url": "https://thepointsguy.com/news/you-can-now-request-an-invite-to-the-amex-centurion-card/?utm_source=openai",
"title": "You can now request an invite to the Amex Centurion card - The Points Guy",
"start_index": 1499,
"end_index": 1626
},
{
"url": "https://www.forbes.com/advisor/credit-cards/reviews/centurion-from-american-express/?utm_source=openai",
"title": "American Express Centurion Black Card Review: What To Expect In 2025 Forbes Advisor",
"start_index": 1824,
"end_index": 1942
}
]
}
]
},
"messages": [ "messages": [
{ {
"content": "Hi there! How can I help you with your DoorDash query? Are you inquiring about a missing item, delivery status, or subscription details?", "version": "v1",
"created_at": null, "chatId": "",
"response_type": "internal", "createdAt": "2025-05-06T07:17:55.790Z",
"role": "assistant", "role": "assistant",
"sender": "DoorDash Support Hub" "tool_calls": [
},
{ {
"content": "Hi there! How can I help you with your DoorDash query? Are you inquiring about a missing item, delivery status, or subscription details?", "id": "ws_6819b79705a08192a819334a0c7900b6004e105788fca541",
"created_at": null, "function": {
"response_type": "external", "name": "web_search",
"role": "assistant", "arguments": "{\"search_id\": \"ws_6819b79705a08192a819334a0c7900b6004e105788fca541\"}"
"sender": "DoorDash Support Hub >> External" },
"type": "function"
} }
], ],
"state": { "agenticSender": "Card Application Process",
"agent_data": [ "agenticResponseType": "internal"
{
"instructions": "## 🧑‍💼 Role:\nYou are responsible for directing DoorDash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which DoorDash-related query they need help with (e.g., 'Are you inquiring about a missing item, delivery status, or subscription details?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Missing Items → [@agent:Missing Items]\n - Delivery Status → [@agent:Delivery Status]\n - Subscription Info → [@agent:Subscription Info]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Missing items queries\n- Delivery status queries\n- Subscription-related queries\n\n❌ Out of Scope:\n- Issues unrelated to DoorDash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Direct queries to specific DoorDash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond DoorDash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with a missing item in my order.\n - **Agent actions**: Call [@agent:Missing Items](#mention)\n\n- **User** : Can you tell me the status of my delivery?\n - **Agent actions**: Call [@agent:Delivery Status](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would you like help with today?\n\n- **User** : Can you reset my account settings?\n - **Agent actions**: Call [@agent:Escalation](#mention)\n\n- **User** : I have a question about my order.\n - **Agent response**: Could you specify if it's about a missing item, delivery status, or subscription details?\n\n- **User** : What are the available subscription plans?\n - **Agent actions**: Call [@agent:Subscription Info](#mention)",
"name": "DoorDash Support Hub"
}, },
{ {
"instructions": "Ensure that the agent response is terse and to the point.", "version": "v1",
"name": "Post process" "chatId": "",
}, "createdAt": "2025-05-06T07:17:55.790Z",
{ "role": "assistant",
"instructions": "Get the user's contact information and let them know that their request has been escalated.", "content": "The American Express Centurion Card, commonly known as the \"Black Card,\" is an invitation-only charge card designed for high-net-worth individuals. To be considered for an invitation, you typically need to meet the following criteria:\n\n- **Existing American Express Relationship**: You should have an active American Express card account.\n\n- **High Annual Spending**: While American Express does not publicly disclose specific spending thresholds, reports suggest that spending between $250,000 and $500,000 annually on your American Express cards may increase your chances of receiving an invitation. ([nerdwallet.com](https://www.nerdwallet.com/article/travel/centurion-black-card?utm_source=openai))\n\n- **Luxury Spending Patterns**: Expenditures on luxury items, such as high-end travel, fine dining, and premium goods, are considered favorable. ([fool.com](https://www.fool.com/the-ascent/credit-cards/articles/your-guide-to-getting-an-amex-black-card/?utm_source=openai))\n\n- **Excellent Credit Score**: A credit score of 800 or higher is typically expected. ([finance.yahoo.com](https://finance.yahoo.com/personal-finance/american-express-centurion-card-213619679.html?utm_source=openai))\n\nIf you believe you meet these criteria, you can express your interest by visiting the official Centurion website and submitting a request for consideration. Please note that submitting a request does not guarantee an invitation, as American Express evaluates each request based on its internal criteria. ([thepointsguy.com](https://thepointsguy.com/news/you-can-now-request-an-invite-to-the-amex-centurion-card/?utm_source=openai))\n\nShould you receive an invitation and choose to accept it, be prepared for the following fees:\n\n- **Initiation Fee**: A one-time fee of $10,000.\n\n- **Annual Fee**: An ongoing annual fee of $5,000. ([forbes.com](https://www.forbes.com/advisor/credit-cards/reviews/centurion-from-american-express/?utm_source=openai))\n\nPlease be aware that the Centurion Card is designed for individuals with substantial financial resources and specific spending habits. If you do not meet these criteria, American Express offers a range of other premium cards that might better suit your needs. ",
"name": "Escalation" "agenticSender": "Card Application Process",
}, "agenticResponseType": "external"
{
"instructions": "## 🧑‍💼 Role:\nHelp users resolve issues with missing items in their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the missing items with the user.\n3. Provide resolution options or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Resolving missing items issues\n\n❌ Out of Scope:\n- Delivery status queries\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use [@tool:get_order_details](#mention) to fetch accurate order information.\n- Provide clear resolution options.\n\n🚫 Don'ts:\n- Assume missing items without user confirmation.\n- Extend the conversation beyond missing items.\n\n# Examples\n- **User** : I didn't receive my fries with my order.\n - **Agent response**: Let me check your order details. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing a drink.\n - **Agent response**: I apologize for the inconvenience. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get the extra sauce I ordered.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My burger is missing from the order.\n - **Agent response**: I'm sorry to hear that. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get my dessert.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
"name": "Missing Items"
},
{
"instructions": "## 🧑‍💼 Role:\nHelp users with queries related to the delivery status of their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Provide the user with the current delivery status.\n3. Offer additional assistance if needed.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Providing delivery status updates\n\n❌ Out of Scope:\n- Resolving missing items issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use [@tool:get_delivery_status](#mention) to fetch accurate delivery information.\n- Provide clear and concise status updates.\n\n🚫 Don'ts:\n- Provide status updates without fetching current information.\n- Extend the conversation beyond delivery status.\n\n# Examples\n- **User** : Where is my order?\n - **Agent response**: Let me check the delivery status for you. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order on the way?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : When will my order arrive?\n - **Agent response**: Let me find out the delivery status. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : How long until my order gets here?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order delayed?\n - **Agent response**: Let me check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
"name": "Delivery Status"
},
{
"instructions": "## 🧑‍💼 Role:\nProvide information and answer questions related to subscriptions using RAG.\n\n---\n## ⚙️ Steps to Follow:\n1. Use RAG to retrieve relevant information about subscriptions.\n2. Answer the user's questions based on the retrieved information.\n3. If the user's query is outside the scope of subscriptions, inform them politely.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Subscription plans and pricing\n- Subscription features and benefits\n\n❌ Out of Scope:\n- Non-subscription-related queries\n- Detailed account management\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use RAG to provide accurate and up-to-date information.\n- Be clear and concise in your responses.\n\n🚫 Don'ts:\n- Provide information without using RAG.\n- Extend the conversation beyond subscription topics.\n\n# Examples\n- **User** : What are the available subscription plans?\n - **Agent response**: Let me check the available subscription plans for you.\n\n- **User** : How much does the premium subscription cost?\n - **Agent response**: I'll find the current pricing for the premium subscription.\n\n- **User** : Can I change my subscription plan?\n - **Agent response**: I'll provide information on how to change your subscription plan.\n\n- **User** : What benefits do I get with a subscription?\n - **Agent response**: Let me retrieve the benefits associated with our subscription plans.\n\n- **User** : Is there a free trial available?\n - **Agent response**: I'll check if there's a free trial available for our subscriptions.",
"name": "Subscription Info"
}
],
"last_agent_name": "DoorDash Support Hub"
},
"tokens_used": {
"completion": 50,
"prompt": 50,
"total": 100
}
} }
]
} }
}

View file

@ -1,6 +1,28 @@
{ {
"messages": [
{
"role": "system",
"content": ""
},
{
"version": "v1",
"chatId": "",
"createdAt": "2025-05-08T12:18:10.212Z",
"role": "assistant",
"content": "How can I help you today?",
"agenticSender": "Blog Writer Hub",
"agenticResponseType": "external"
},
{
"role": "user",
"content": "write a blog on bitcoin",
"version": "v1",
"chatId": "",
"createdAt": "2025-05-08T12:18:15.023Z"
}
],
"lastRequest": { "lastRequest": {
"projectId": "4ebd5e81-010a-4bc6-91e0-0aa98173dbac", "projectId": "e088bfb0-793d-4da8-8e3d-001bfb6fb647",
"messages": [ "messages": [
{ {
"content": "", "content": "",
@ -9,98 +31,103 @@
"tool_calls": null, "tool_calls": null,
"tool_call_id": null, "tool_call_id": null,
"tool_name": null "tool_name": null
},
{
"content": "How can I help you today?",
"role": "assistant",
"sender": "Blog Writer Hub",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "external"
},
{
"content": "write a blog on bitcoin",
"role": "user",
"sender": null,
"tool_calls": null,
"tool_call_id": null,
"tool_name": null
} }
], ],
"state": { "state": {
"last_agent_name": "DoorDash Support Hub" "last_agent_name": "Blog Writer Hub",
"tokens": {
"total": 0,
"prompt": 0,
"completion": 0
},
"turn_messages": [
{
"content": "How can I help you today?",
"role": "assistant",
"sender": "Blog Writer Hub",
"tool_calls": null,
"tool_call_id": null,
"tool_name": null,
"response_type": "external"
}
]
}, },
"agents": [ "agents": [
{ {
"name": "DoorDash Support Hub", "name": "Blog Writer Hub",
"type": "conversation", "type": "conversation",
"description": "Hub agent to manage DoorDash-related queries.", "description": "Hub agent to orchestrate the blog writing process from research to final blog post.",
"instructions": "## 🧑‍💼 Role:\nYou are responsible for directing DoorDash-related queries to appropriate agents.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask which DoorDash-related query they need help with (e.g., 'Are you inquiring about a missing item, delivery status, or subscription details?').\n2. If the query matches a specific task, direct the user to the corresponding agent:\n - Missing Items → [@agent:Missing Items]\n - Delivery Status → [@agent:Delivery Status]\n - Subscription Info → [@agent:Subscription Info]\n3. If the query doesn't match any specific task, respond with 'I'm sorry, I didn't understand. Could you clarify your request?' or escalate to human support.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Missing items queries\n- Delivery status queries\n- Subscription-related queries\n\n❌ Out of Scope:\n- Issues unrelated to DoorDash\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Direct queries to specific DoorDash agents promptly.\n- Call [@agent:Escalation] agent for unrecognized queries.\n\n🚫 Don'ts:\n- Engage in detailed support.\n- Extend the conversation beyond DoorDash.\n- Provide user-facing text such as 'I will connect you now...' when calling another agent\n\n# Examples\n- **User** : I need help with a missing item in my order.\n - **Agent actions**: Call [@agent:Missing Items](#mention)\n\n- **User** : Can you tell me the status of my delivery?\n - **Agent actions**: Call [@agent:Delivery Status](#mention)\n\n- **User** : How are you today?\n - **Agent response**: I'm doing great. What would you like help with today?\n\n- **User** : Can you reset my account settings?\n - **Agent actions**: Call [@agent:Escalation](#mention)\n\n- **User** : I have a question about my order.\n - **Agent response**: Could you specify if it's about a missing item, delivery status, or subscription details?\n\n- **User** : What are the available subscription plans?\n - **Agent actions**: Call [@agent:Subscription Info](#mention)", "instructions": "## 🧑‍💼 Role:\nYou are the hub agent responsible for orchestrating the blog writing process for the user.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet the user and ask for the blog topic.\n2. FIRST: Send the topic to [@agent:Research Agent] to gather and compile research notes.\n3. Wait for the complete research notes from the Research Agent.\n4. THEN: Send the research notes to [@agent:Outline Agent] to generate a detailed outline.\n5. Wait for the outline from the Outline Agent.\n6. THEN: Send the outline to [@agent:Writing Agent] to write the full blog post (1000+ words).\n7. Once the blog post is ready, return it to the user.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Orchestrating the sequential process of research, outlining, and writing\n- Returning the final blog post to the user\n\n❌ Out of Scope:\n- Performing research, outlining, or writing directly\n- Handling requests unrelated to blog writing\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Follow the strict sequence: Research → Outline → Writing\n- Wait for each agent's complete response before proceeding\n- Only interact with the user for topic input and final blog post delivery\n\n🚫 Don'ts:\n- Do not perform research, outlining, or writing yourself\n- Do not try to get responses from multiple agents simultaneously\n- CRITICAL: The system does not support more than 1 tool call in a single output when the tool call is about transferring to another agent (a handoff). You must only put out 1 transfer related tool call in one output.\n\n# Examples\n- **User** : I want a blog post about 'Benefits of Remote Work'.\n - **Agent actions**: Call [@agent:Research Agent]\n\n- **Agent receives research notes** :\n - **Agent actions**: Call [@agent:Outline Agent]\n\n- **Agent receives outline** :\n - **Agent actions**: Call [@agent:Writing Agent]\n\n- **Agent receives blog post** :\n - **Agent response**: Here is your completed blog post on 'Benefits of Remote Work': [Full blog post]\n\n- **User** : Can you write a blog post on 'AI in Healthcare'?\n - **Agent actions**: Call [@agent:Research Agent](#mention)\n\n- **User** : Hi!\n - **Agent response**: Hello! What blog topic would you like to write about today?",
"model": "gpt-4o", "model": "gpt-4.1",
"controlType": "retain", "controlType": "retain",
"ragK": 3, "ragK": 3,
"ragReturnType": "chunks", "ragReturnType": "chunks",
"outputVisibility": "user_facing",
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [ "connectedAgents": [
"Missing Items", "Research Agent",
"Delivery Status", "Outline Agent",
"Subscription Info", "Writing Agent"
"Escalation"
] ]
}, },
{ {
"name": "Post process", "name": "Research Agent",
"type": "post_process", "type": "conversation",
"description": "", "description": "Researches the given blog topic and compiles relevant information.",
"instructions": "Ensure that the agent response is terse and to the point.", "instructions": "## 🧑‍💼 Role:\nYou are responsible for researching the provided blog topic and compiling relevant, up-to-date information.\n\n---\n## ⚙️ Steps to Follow:\n1. Use the [@tool:web_search] tool to gather information about the topic.\n2. Summarize and compile the most important and recent findings, statistics, and facts relevant to the topic.\n3. Return a concise, well-organized compilation of research notes (not an outline or blog post).\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Researching the topic using web sources\n- Compiling factual, relevant information\n\n❌ Out of Scope:\n- Creating outlines or writing the blog post\n- Providing opinions or unverified information\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use multiple reputable sources\n- Organize findings clearly\n\n🚫 Don'ts:\n- Do not create outlines or write the blog post\n- Do not include personal opinions\n\n# Examples\n- **User** : Research the topic 'Benefits of Remote Work'.\n - **Agent actions**: Call [@tool:web_search](#mention)\n - **Agent response**: \"Compiled research notes: 1. Increased productivity: Studies show remote workers are 13% more productive (Stanford, 2020). 2. Cost savings: Companies save on office space and utilities. 3. Improved work-life balance...\"",
"model": "gpt-4o-mini", "model": "gpt-4.1",
"controlType": "retain", "controlType": "retain",
"ragK": 3, "ragK": 3,
"ragReturnType": "chunks", "ragReturnType": "chunks",
"outputVisibility": "internal",
"tools": [
"web_search"
],
"prompts": [],
"connectedAgents": []
},
{
"name": "Outline Agent",
"type": "conversation",
"description": "Creates a detailed bullet-point outline for the blog post based on research notes.",
"instructions": "## 🧑‍💼 Role:\nYou are responsible for creating a detailed outline for the blog post using the compiled research notes.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the compiled research notes.\n2. Create a logical, well-structured bullet-point outline for the blog post (including introduction, main sections, and conclusion).\n3. Ensure the outline covers all key points from the research.\n4. Return only the outline (not the full blog post).\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Creating outlines for blog posts\n\n❌ Out of Scope:\n- Conducting research\n- Writing the full blog post\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Ensure logical flow and coverage of all research points\n- Use clear, concise bullet points\n\n🚫 Don'ts:\n- Do not write full paragraphs or the blog post itself\n\n# Examples\n- **User** : Create an outline for a blog on 'Benefits of Remote Work'.\n - **Agent response**: \"Outline: 1. Introduction 2. Increased Productivity 3. Cost Savings for Companies 4. Improved Work-Life Balance 5. Challenges and Solutions 6. Conclusion\"",
"model": "gpt-4.1",
"controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"outputVisibility": "internal",
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": []
}, },
{ {
"name": "Escalation", "name": "Writing Agent",
"type": "escalation", "type": "conversation",
"description": "", "description": "Writes a full blog post (1000+ words) based on the provided outline.",
"instructions": "Get the user's contact information and let them know that their request has been escalated.", "instructions": "## 🧑‍💼 Role:\nYou are responsible for writing a complete, well-structured blog post based on the provided outline.\n\n---\n## ⚙️ Steps to Follow:\n1. Receive the outline for the blog post.\n2. Write a detailed, engaging blog post that follows the outline and incorporates all key points.\n3. Ensure the blog post is at least 1000 words.\n4. Return only the completed blog post.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Writing full blog posts based on outlines\n\n❌ Out of Scope:\n- Creating outlines or conducting research\n- Writing posts shorter than 1000 words\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Ensure clarity, coherence, and logical flow\n- Expand on each outline point with relevant details\n- Use engaging and professional language\n\n🚫 Don'ts:\n- Do not skip any outline sections\n- Do not write less than 1000 words\n\n# Examples\n- **User** : Write a blog post on 'Benefits of Remote Work' using the provided outline.\n - **Agent response**: \"[Full blog post, 1000+ words, covering all outline points]\"",
"model": "gpt-4o-mini", "model": "gpt-4.1",
"controlType": "retain", "controlType": "retain",
"ragK": 3, "ragK": 3,
"ragReturnType": "chunks", "ragReturnType": "chunks",
"tools": [], "outputVisibility": "internal",
"prompts": [],
"connectedAgents": []
},
{
"name": "Missing Items",
"type": "conversation",
"description": "Agent to assist users with missing items in their orders.",
"instructions": "## 🧑‍💼 Role:\nHelp users resolve issues with missing items in their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the order details using the [@tool:get_order_details] tool.\n2. Confirm the missing items with the user.\n3. Provide resolution options or escalate if unresolved.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Resolving missing items issues\n\n❌ Out of Scope:\n- Delivery status queries\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use [@tool:get_order_details](#mention) to fetch accurate order information.\n- Provide clear resolution options.\n\n🚫 Don'ts:\n- Assume missing items without user confirmation.\n- Extend the conversation beyond missing items.\n\n# Examples\n- **User** : I didn't receive my fries with my order.\n - **Agent response**: Let me check your order details. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My order is missing a drink.\n - **Agent response**: I apologize for the inconvenience. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get the extra sauce I ordered.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : My burger is missing from the order.\n - **Agent response**: I'm sorry to hear that. Let's verify your order details. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)\n\n- **User** : I didn't get my dessert.\n - **Agent response**: Let's check your order details to confirm. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_order_details](#mention)",
"model": "gpt-4o",
"controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"tools": [
"get_order_details"
],
"prompts": [],
"connectedAgents": []
},
{
"name": "Delivery Status",
"type": "conversation",
"description": "Agent to assist users with delivery status queries.",
"instructions": "## 🧑‍💼 Role:\nHelp users with queries related to the delivery status of their orders.\n\n---\n## ⚙️ Steps to Follow:\n1. Fetch the delivery status using the [@tool:get_delivery_status] tool.\n2. Provide the user with the current delivery status.\n3. Offer additional assistance if needed.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Providing delivery status updates\n\n❌ Out of Scope:\n- Resolving missing items issues\n- General knowledge queries\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use [@tool:get_delivery_status](#mention) to fetch accurate delivery information.\n- Provide clear and concise status updates.\n\n🚫 Don'ts:\n- Provide status updates without fetching current information.\n- Extend the conversation beyond delivery status.\n\n# Examples\n- **User** : Where is my order?\n - **Agent response**: Let me check the delivery status for you. Could you please provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order on the way?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : When will my order arrive?\n - **Agent response**: Let me find out the delivery status. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : How long until my order gets here?\n - **Agent response**: I'll check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)\n\n- **User** : Is my order delayed?\n - **Agent response**: Let me check the delivery status for you. Could you provide your order ID?\n - **Agent actions**: Call [@tool:get_delivery_status](#mention)",
"model": "gpt-4o",
"controlType": "retain",
"ragK": 3,
"ragReturnType": "chunks",
"tools": [
"get_delivery_status"
],
"prompts": [],
"connectedAgents": []
},
{
"name": "Subscription Info",
"type": "conversation",
"description": "Agent to assist users with subscription-related queries.",
"instructions": "## 🧑‍💼 Role:\nProvide information and answer questions related to subscriptions using RAG.\n\n---\n## ⚙️ Steps to Follow:\n1. Use RAG to retrieve relevant information about subscriptions.\n2. Answer the user's questions based on the retrieved information.\n3. If the user's query is outside the scope of subscriptions, inform them politely.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Subscription plans and pricing\n- Subscription features and benefits\n\n❌ Out of Scope:\n- Non-subscription-related queries\n- Detailed account management\n\n---\n## 📋 Guidelines:\n✔ Dos:\n- Use RAG to provide accurate and up-to-date information.\n- Be clear and concise in your responses.\n\n🚫 Don'ts:\n- Provide information without using RAG.\n- Extend the conversation beyond subscription topics.\n\n# Examples\n- **User** : What are the available subscription plans?\n - **Agent response**: Let me check the available subscription plans for you.\n\n- **User** : How much does the premium subscription cost?\n - **Agent response**: I'll find the current pricing for the premium subscription.\n\n- **User** : Can I change my subscription plan?\n - **Agent response**: I'll provide information on how to change your subscription plan.\n\n- **User** : What benefits do I get with a subscription?\n - **Agent response**: Let me retrieve the benefits associated with our subscription plans.\n\n- **User** : Is there a free trial available?\n - **Agent response**: I'll check if there's a free trial available for our subscriptions.",
"model": "gpt-4o",
"controlType": "relinquish_to_parent",
"ragDataSources": [
"67e1612510540d9027909e10"
],
"ragK": 3,
"ragReturnType": "content",
"tools": [], "tools": [],
"prompts": [], "prompts": [],
"connectedAgents": [] "connectedAgents": []
@ -108,59 +135,19 @@
], ],
"tools": [ "tools": [
{ {
"name": "get_order_details", "name": "web_search",
"description": "Tool to fetch the user's order details.", "description": "Fetch information from the web based on chat context",
"parameters": { "parameters": {
"type": "object", "type": "object",
"properties": { "properties": {}
"order_id": {
"type": "string",
"description": "The unique identifier for the order."
}
}, },
"required": [ "isLibrary": true
"order_id"
]
}
},
{
"name": "get_delivery_status",
"description": "Tool to fetch the delivery status of an order.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The unique identifier for the order."
}
},
"required": [
"order_id"
]
},
"mockTool": true,
"mockInstructions": "Give a mock response for a door dash order delivery status."
} }
], ],
"prompts": [ "prompts": [],
{ "startAgent": "Blog Writer Hub",
"name": "Style prompt", "mcpServers": [],
"type": "style_prompt", "toolWebhookUrl": ""
"prompt": "You should be empathetic and helpful."
}, },
{ "lastResponse": null
"name": "Greeting",
"type": "greeting",
"prompt": "Hello! How can I help you?"
}
],
"startAgent": "DoorDash Support Hub",
"mcpServers": [
{
"name": "delivery",
"url": "http://localhost:8000/sse"
}
],
"toolWebhookUrl": "http://127.0.0.1:4020/tool_call"
}
} }

View file

@ -56,6 +56,8 @@ services:
- PROVIDER_BASE_URL=${PROVIDER_BASE_URL} - PROVIDER_BASE_URL=${PROVIDER_BASE_URL}
- PROVIDER_API_KEY=${PROVIDER_API_KEY} - PROVIDER_API_KEY=${PROVIDER_API_KEY}
- PROVIDER_DEFAULT_MODEL=${PROVIDER_DEFAULT_MODEL} - PROVIDER_DEFAULT_MODEL=${PROVIDER_DEFAULT_MODEL}
- MAX_CALLS_PER_CHILD_AGENT=${MAX_CALLS_PER_CHILD_AGENT}
- ENABLE_TRACING=${ENABLE_TRACING}
restart: unless-stopped restart: unless-stopped
copilot: copilot: