From fa0d49298656ba93be56b947f20b716a9a30762e Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Thu, 20 Mar 2025 14:22:15 -0700 Subject: [PATCH 1/6] Add `--host` to vite in dev mode In order to work in dev container --- src/web/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/package.json b/src/web/package.json index 6e0ee420..7cb5ca9d 100644 --- a/src/web/package.json +++ b/src/web/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" From be1e713d3548d038226fba9aaba45d9e4b828901 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Fri, 21 Mar 2025 10:57:22 -0700 Subject: [PATCH 2/6] Bumped TPM to 150 --- infra/ai.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/ai.yaml b/infra/ai.yaml index 08af4bf9..adcaf2e8 100644 --- a/infra/ai.yaml +++ b/infra/ai.yaml @@ -14,11 +14,11 @@ deployments: name: gpt-4o sku: name: "GlobalStandard" - capacity: 80 + capacity: 150 - name: gpt-4-evals model: format: OpenAI name: gpt-4o-mini sku: name: "GlobalStandard" - capacity: 80 + capacity: 150 From 2e41f7251940006f9f2eb18ee1aeb9800c8ec7a5 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Wed, 26 Mar 2025 09:34:32 +0900 Subject: [PATCH 3/6] Prompty file doesn't need to be loaded twice --- src/api/agents/researcher/researcher.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/agents/researcher/researcher.py b/src/api/agents/researcher/researcher.py index 7f0af8f9..0cdb1b52 100644 --- a/src/api/agents/researcher/researcher.py +++ b/src/api/agents/researcher/researcher.py @@ -45,8 +45,6 @@ def execute_research(instructions: str, feedback: str = "No feedback"): # Initialize agent bing tool and add the connection id bing = BingGroundingTool(connection_id=conn_id) - prompt_template = PromptTemplate.from_prompty(file_path="researcher.prompty") - # Create agent with the bing tool and process assistant run with project_client: agent = project_client.agents.create_agent( From e2aad0063f86574f6d76270cbb2743e0f5203abd Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Wed, 26 Mar 2025 11:36:34 +0900 Subject: [PATCH 4/6] Init researcher agent at startup time Moved to startup time: - AI Project connection - AI project init - Bing connection - Bing grounding tool - Extract instructions from system message in Prompty template - Agent init - Reuse agent if it already exists instead of creating a new one - Moved researcher feedback to separate assistant message in Prompty template Bug fixes: - feedback from editor is now passed to researcher agent --- src/api/agents/researcher/researcher.prompty | 13 ++- src/api/agents/researcher/researcher.py | 112 ++++++++++++------- src/api/orchestrator.py | 6 +- 3 files changed, 80 insertions(+), 51 deletions(-) diff --git a/src/api/agents/researcher/researcher.prompty b/src/api/agents/researcher/researcher.prompty index b98918a5..6e58b193 100644 --- a/src/api/agents/researcher/researcher.prompty +++ b/src/api/agents/researcher/researcher.prompty @@ -98,11 +98,6 @@ Return the results as a list of JSON objects with the following structure: - It's important that you only return the json object, nothing else!! Do not return any additional text. - Make sure to return at least 4 articles, but no more than 5. -# Feedback -Use this feedback to help you refine your queries and responses - if there is any feedback: - -{{feedback}} - # Market Codes The following are the market codes for the countries and regions that are supported by the Microsoft Bing API and should be used when formulating your queries. Use the language @@ -127,5 +122,11 @@ Swedish, sv-SE Turkish, tr-TR English, en-US +assistant: +# Feedback +Use this feedback to help you refine your queries and responses - if there is any feedback: + +{{feedback}} + user: -{{instructions}} \ No newline at end of file +{{instructions}} diff --git a/src/api/agents/researcher/researcher.py b/src/api/agents/researcher/researcher.py index 0cdb1b52..0bae062f 100644 --- a/src/api/agents/researcher/researcher.py +++ b/src/api/agents/researcher/researcher.py @@ -19,56 +19,86 @@ # At the moment, it should be in the format ";;;" # Customer needs to login to Azure subscription via Azure CLI and set the environment variables - -@trace -def execute_research(instructions: str, feedback: str = "No feedback"): - - ai_project_conn_str = os.getenv("AZURE_LOCATION")+".api.azureml.ms;"+os.getenv("AZURE_SUBSCRIPTION_ID")+";"+os.getenv("AZURE_RESOURCE_GROUP")+";"+os.getenv("AZURE_AI_PROJECT_NAME") - - project_client = AIProjectClient.from_connection_string( - credential=DefaultAzureCredential(), - conn_str=ai_project_conn_str, +ai_project_conn_str = os.getenv("AZURE_LOCATION")+".api.azureml.ms;"+os.getenv("AZURE_SUBSCRIPTION_ID")+";"+os.getenv("AZURE_RESOURCE_GROUP")+";"+os.getenv("AZURE_AI_PROJECT_NAME") +print(f"Connection string: {ai_project_conn_str}") + +project_client = AIProjectClient.from_connection_string( + credential=DefaultAzureCredential(), + conn_str=ai_project_conn_str, +) +print(f"Project client created: {project_client}") + +prompt_template = PromptTemplate.from_prompty(file_path="researcher.prompty") + +bing_connection = project_client.connections.get( + connection_name='bing-connection' +) +conn_id = bing_connection.id + +# Initialize agent bing tool and add the connection id +bing = BingGroundingTool(connection_id=conn_id) + +# Extract instructions from system message in Prompty template +messages = prompt_template.create_messages(instructions='', feedback='') +system_messages = [m for m in messages if m['role'] == 'system'] +agent_system_instructions = system_messages[0]['content'] + +AGENT_NAME = "contoso-creative-writer" +found_agent = None +all_agents_list = project_client.agents.list_agents().data +for a in all_agents_list: + if a.name == AGENT_NAME: + found_agent = a + break + +model_name = "gpt-4" +if found_agent: + # Update the existing agent to use new tools + agent = project_client.agents.update_agent( + assistant_id=found_agent.id, + model=model_name, + instructions=agent_system_instructions, + tools=bing.definitions, ) - - prompt_template = PromptTemplate.from_prompty(file_path="researcher.prompty") - - - instructions = instructions - feedback= feedback - messages = prompt_template.create_messages(instructions=instructions, feedback=feedback) - - bing_connection = project_client.connections.get( - connection_name='bing-connection' + print(f"reusing agent > {agent.name} (id: {agent.id})") +else: + agent = project_client.agents.create_agent( + model=model_name, + name=AGENT_NAME, + instructions=agent_system_instructions, + tools=bing.definitions, ) - conn_id = bing_connection.id + print(f"creating agent > {agent.name} (id: {agent.id})") + +@trace +def execute_research(instructions: str, feedback: str = None): - # Initialize agent bing tool and add the connection id - bing = BingGroundingTool(connection_id=conn_id) + if not feedback: + feedback = "No feedback" # Create agent with the bing tool and process assistant run with project_client: - agent = project_client.agents.create_agent( - model="gpt-4", - name="my-assistant", - instructions=messages[0]['content'], - tools=bing.definitions, - ) - - print(f"Created agent, ID: {agent.id}") # Create thread for communication thread = project_client.agents.create_thread() print(f"Created thread, ID: {thread.id}") - # Create message to thread - message = project_client.agents.create_message( - thread_id=thread.id, - role="user", - content=instructions, - ) - print(f"Created message, ID: {message.id}") + # Create assistant and user messages from Prompty template + messages = prompt_template.create_messages(instructions=instructions, feedback=feedback) + thread_messages = [m for m in messages if m['role'] in ['assistant', 'user']] + for message in thread_messages: + content = message['content'] + role = message['role'] + + # Create message to thread + message = project_client.agents.create_message( + thread_id=thread.id, + role=role, + content=content, + ) + print(f"Created {role} message, ID: {message.id}") - # # Create and process agent run in thread with tools + # Create and process agent run in thread with tools # run = project_client.agents.create_stream(thread_id=thread.id, assistant_id=agent.id) def is_rate_limited(run): # Check if the run failed due to rate limit @@ -98,7 +128,7 @@ def run_agent(): print('') # Delete the assistant when done - project_client.agents.delete_agent(agent.id) + #project_client.agents.delete_agent(agent.id) # Fetch and log all messages messages = project_client.agents.list_messages(thread_id=thread.id) @@ -115,8 +145,8 @@ def run_agent(): return research @trace -def research(instructions: str, feedback: str = "No feedback"): - r = execute_research(instructions=instructions) +def research(instructions: str, feedback: str = None): + r = execute_research(instructions=instructions, feedback=feedback) research = { "web": r, "entities": [], diff --git a/src/api/orchestrator.py b/src/api/orchestrator.py index 0c48d57a..f60b2f8d 100644 --- a/src/api/orchestrator.py +++ b/src/api/orchestrator.py @@ -68,12 +68,10 @@ def building_agents_message(): @trace def create(research_context, product_context, assignment_context, evaluate=False): - feedback = "No Feedback" - yield building_agents_message() yield start_message("researcher") - research_result = researcher.research(research_context, feedback) + research_result = researcher.research(research_context) yield complete_message("researcher", research_result) yield start_message("marketing") @@ -88,7 +86,7 @@ def create(research_context, product_context, assignment_context, evaluate=False product_context, product_result, assignment_context, - feedback, + feedback = "No Feedback", ) full_result = " " From 049865b99fc53027a6fb9832cfedcea770af29b4 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Wed, 26 Mar 2025 11:44:17 +0900 Subject: [PATCH 5/6] Removed commented code --- src/api/orchestrator.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/api/orchestrator.py b/src/api/orchestrator.py index f60b2f8d..b0362fa8 100644 --- a/src/api/orchestrator.py +++ b/src/api/orchestrator.py @@ -8,7 +8,6 @@ from agents.researcher import researcher from agents.product import product from agents.writer import writer -# from agents.designer import designer from agents.editor import editor from evaluate.evaluators import evaluate_article_in_background from prompty.tracer import trace, Tracer, console_tracer, PromptyTracer @@ -96,11 +95,6 @@ def create(research_context, product_context, assignment_context, evaluate=False processed_writer_result = writer.process(full_result) - # send article to the designer, to generate an image for the blog - # yield start_message("designer") - # designer_response = designer.design(processed_writer_result['article']) - # yield complete_message("designer", [f"Image stored in {designer_response}"]) - # Then send it to the editor, to decide if it's good or not yield start_message("editor") editor_response = editor.edit(processed_writer_result['article'], processed_writer_result["feedback"]) From 38f75b0c79bcd7ebc2619d86ef64199f5d35f78d Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Wed, 26 Mar 2025 11:55:07 +0900 Subject: [PATCH 6/6] Keeping the AI Project opened We're reusing the AI project between requests --- src/api/agents/researcher/researcher.py | 127 ++++++++++++------------ 1 file changed, 62 insertions(+), 65 deletions(-) diff --git a/src/api/agents/researcher/researcher.py b/src/api/agents/researcher/researcher.py index 0bae062f..0ff35284 100644 --- a/src/api/agents/researcher/researcher.py +++ b/src/api/agents/researcher/researcher.py @@ -76,73 +76,70 @@ def execute_research(instructions: str, feedback: str = None): if not feedback: feedback = "No feedback" - # Create agent with the bing tool and process assistant run - with project_client: - - # Create thread for communication - thread = project_client.agents.create_thread() - print(f"Created thread, ID: {thread.id}") - - # Create assistant and user messages from Prompty template - messages = prompt_template.create_messages(instructions=instructions, feedback=feedback) - thread_messages = [m for m in messages if m['role'] in ['assistant', 'user']] - for message in thread_messages: - content = message['content'] - role = message['role'] - - # Create message to thread - message = project_client.agents.create_message( - thread_id=thread.id, - role=role, - content=content, - ) - print(f"Created {role} message, ID: {message.id}") - - # Create and process agent run in thread with tools - # run = project_client.agents.create_stream(thread_id=thread.id, assistant_id=agent.id) - def is_rate_limited(run): - # Check if the run failed due to rate limit - if run.status == "failed" and run.last_error and run.last_error.get('code') == 'rate_limit_exceeded': - print(f"Run failed: {run.last_error}") - return True # Indicates Tenacity should retry - return False # No retry needed - - @retry( - retry=retry_if_result(is_rate_limited), - wait=wait_exponential(multiplier=1, min=4, max=60), - stop=stop_after_attempt(6) + # Create thread for communication + thread = project_client.agents.create_thread() + print(f"Created thread, ID: {thread.id}") + + # Create assistant and user messages from Prompty template + messages = prompt_template.create_messages(instructions=instructions, feedback=feedback) + thread_messages = [m for m in messages if m['role'] in ['assistant', 'user']] + for message in thread_messages: + content = message['content'] + role = message['role'] + + # Create message to thread + message = project_client.agents.create_message( + thread_id=thread.id, + role=role, + content=content, ) - def run_agent(): - # Create and process agent run in thread with tools - run = project_client.agents.create_and_process_run(thread_id=thread.id, assistant_id=agent.id) - print(f"Run finished with status: {run.status}") - return run - - run = run_agent() - # Retrieve run step details to get Bing Search query link - # To render the webpage, we recommend you replace the endpoint of Bing search query URLs with `www.bing.com` and your Bing search query URL would look like "https://www.bing.com/search?q={search query}" - run_steps = project_client.agents.list_run_steps(run_id=run.id, thread_id=thread.id) - run_steps_data = run_steps['data'] - - print(f"Agent created and now researching...") - print('') - - # Delete the assistant when done - #project_client.agents.delete_agent(agent.id) - - # Fetch and log all messages - messages = project_client.agents.list_messages(thread_id=thread.id) - # print(f"Messages: {messages}") + print(f"Created {role} message, ID: {message.id}") + + # Create and process agent run in thread with tools + # run = project_client.agents.create_stream(thread_id=thread.id, assistant_id=agent.id) + def is_rate_limited(run): + # Check if the run failed due to rate limit + if run.status == "failed" and run.last_error and run.last_error.get('code') == 'rate_limit_exceeded': + print(f"Run failed: {run.last_error}") + return True # Indicates Tenacity should retry + return False # No retry needed + + @retry( + retry=retry_if_result(is_rate_limited), + wait=wait_exponential(multiplier=1, min=4, max=60), + stop=stop_after_attempt(6) + ) + def run_agent(): + # Create and process agent run in thread with tools + run = project_client.agents.create_and_process_run(thread_id=thread.id, assistant_id=agent.id) + print(f"Run finished with status: {run.status}") + return run + + run = run_agent() + # Retrieve run step details to get Bing Search query link + # To render the webpage, we recommend you replace the endpoint of Bing search query URLs with `www.bing.com` and your Bing search query URL would look like "https://www.bing.com/search?q={search query}" + run_steps = project_client.agents.list_run_steps(run_id=run.id, thread_id=thread.id) + run_steps_data = run_steps['data'] + + print(f"Agent created and now researching...") + print('') + + # Delete the assistant when done + #project_client.agents.delete_agent(agent.id) + + # Fetch and log all messages + messages = project_client.agents.list_messages(thread_id=thread.id) + # print(f"Messages: {messages}") + research_response = messages.data[0]['content'][0]['text']['value'] + try: + json_r = json.loads(research_response) + except: + print('retrying') research_response = messages.data[0]['content'][0]['text']['value'] - try: - json_r = json.loads(research_response) - except: - print('retrying') - research_response = messages.data[0]['content'][0]['text']['value'] - json_r = json.loads(research_response) - research = json_r['web'] - print('research succesfully completed') - return research + json_r = json.loads(research_response) + research = json_r['web'] + print('research succesfully completed') + return research @trace def research(instructions: str, feedback: str = None):