Skip to content

Commit 7c8801c

Browse files
authored
Improves form-filler instructions (microsoft#280)
Improves form-filler instructions: - improves ability to identity and resolve conflicts - improves ability to handle multiple files Extends form-field model: - to recognize "sections" in forms, for more context in extracted data, and improved populated-form rendering - renders description and instructions in the populated-form Additionally: - further moves away from "agent" name to "extension", by moving out of the agents directory - adds support for PNG files (screenshots, etc.) - adds timestamps to user messages for current-date awareness - fixes links in the app for already-copied conversations
1 parent 47cdd69 commit 7c8801c

File tree

18 files changed

+280
-189
lines changed

18 files changed

+280
-189
lines changed

assistants/prospector-assistant/assistant/agents/form_fill_extension/steps/extract_form_fields_step.py

Lines changed: 0 additions & 100 deletions
This file was deleted.

assistants/prospector-assistant/assistant/chat.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import deepmerge
1616
import openai_client
17+
from assistant_extensions.ai_clients.model import CompletionMessageImageContent
1718
from assistant_extensions.attachments import AttachmentsExtension
1819
from content_safety.evaluators import CombinedContentSafetyEvaluator
1920
from openai.types.chat import ChatCompletionMessageParam
@@ -37,8 +38,8 @@
3738
from . import legacy
3839
from .agents.artifact_agent import Artifact, ArtifactAgent, ArtifactConversationInspectorStateProvider
3940
from .agents.document_agent import DocumentAgent
40-
from .agents.form_fill_extension import FormFillExtension, LLMConfig
4141
from .config import AssistantConfigModel
42+
from .form_fill_extension import FormFillExtension, LLMConfig
4243

4344
logger = logging.getLogger(__name__)
4445

@@ -131,8 +132,8 @@ async def on_chat_message_created(
131132
- @assistant.events.conversation.message.on_created
132133
"""
133134

134-
# update the participant status to indicate the assistant is thinking
135-
async with send_error_message_on_exception(context), context.set_status("thinking..."):
135+
# update the participant status to indicate the assistant is responding
136+
async with send_error_message_on_exception(context), context.set_status("responding..."):
136137
#
137138
# NOTE: we're experimenting with agents, if they are enabled, use them to respond to the conversation
138139
#
@@ -183,7 +184,7 @@ async def on_conversation_created(context: ConversationContext) -> None:
183184

184185

185186
async def welcome_message_form_fill(context: ConversationContext) -> None:
186-
async with send_error_message_on_exception(context), context.set_status("thinking..."):
187+
async with send_error_message_on_exception(context), context.set_status("responding..."):
187188
await form_fill_execute(context, None)
188189

189190

@@ -193,7 +194,7 @@ async def welcome_message_create_document(
193194
message: ConversationMessage | None,
194195
metadata: dict[str, Any],
195196
) -> None:
196-
async with send_error_message_on_exception(context), context.set_status("thinking..."):
197+
async with send_error_message_on_exception(context), context.set_status("responding..."):
197198
await create_document_execute(config, context, message, metadata)
198199

199200

@@ -223,6 +224,7 @@ async def form_fill_execute(context: ConversationContext, message: ConversationM
223224
Execute the form fill agent to respond to the conversation message.
224225
"""
225226
config = await assistant_config.get(context.assistant)
227+
participants = await context.get_participants(include_inactive=True)
226228
await form_fill_extension.execute(
227229
llm_config=LLMConfig(
228230
openai_client_factory=lambda: openai_client.create_client(config.service_config),
@@ -231,7 +233,7 @@ async def form_fill_execute(context: ConversationContext, message: ConversationM
231233
),
232234
config=config.agents_config.form_fill_agent,
233235
context=context,
234-
latest_user_message=message.content if message else None,
236+
latest_user_message=_format_message(message, participants.participants) if message else None,
235237
latest_attachment_filenames=message.filenames if message else [],
236238
get_attachment_content=form_fill_extension_get_attachment(context, config),
237239
)
@@ -251,8 +253,26 @@ async def get(filename: str) -> str:
251253
if not messages:
252254
return ""
253255

254-
# filter down to the messages that contain the attachment (ie. don't include the system messages)
255-
return "\n\n".join((str(message.content) for message in messages if "<ATTACHMENT>" in str(message.content)))
256+
# filter down to the message with the attachment
257+
user_message = next(
258+
(message for message in messages if "<ATTACHMENT>" in str(message)),
259+
None,
260+
)
261+
if not user_message:
262+
return ""
263+
264+
content = user_message.content
265+
match content:
266+
case str():
267+
return content
268+
269+
case list():
270+
for part in content:
271+
match part:
272+
case CompletionMessageImageContent():
273+
return part.data
274+
275+
return ""
256276

257277
return get
258278

assistants/prospector-assistant/assistant/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from . import helpers
1010
from .agents.artifact_agent import ArtifactAgentConfigModel
11-
from .agents.form_fill_extension import FormFillConfig
11+
from .form_fill_extension import FormFillConfig
1212

1313
# The semantic workbench app uses react-jsonschema-form for rendering
1414
# dynamic configuration forms based on the configuration model and UI schema
Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,31 +78,35 @@ def build_step_context(config: ConfigT) -> Context[ConfigT]:
7878

7979
case state.FormFillExtensionMode.extract_form_fields:
8080
file_content = await get_attachment_content(agent_state.form_filename)
81+
attachment = UserAttachment(filename=agent_state.form_filename, content=file_content)
8182
result = await extract_form_fields_step.execute(
8283
step_context=build_step_context(config.extract_form_fields_config),
83-
file_content=file_content,
84+
potential_form_attachment=attachment,
8485
)
8586

8687
match result:
8788
case extract_form_fields_step.CompleteResult():
88-
await _send_message(context, result.message, result.debug)
89+
await _send_message(context, result.message, result.debug, MessageType.notice)
8990

90-
agent_state.extracted_form_title = result.extracted_form_title
91-
agent_state.extracted_form_fields = result.extracted_form_fields
91+
agent_state.extracted_form = result.extracted_form
9292
agent_state.mode = state.FormFillExtensionMode.fill_form_step
9393

9494
continue
9595

9696
case _:
9797
await _handle_incomplete_result(context, result)
98+
99+
agent_state.mode = state.FormFillExtensionMode.acquire_form_step
98100
return
99101

100102
case state.FormFillExtensionMode.fill_form_step:
103+
if agent_state.extracted_form is None:
104+
raise ValueError("extracted_form is None")
105+
101106
result = await fill_form_step.execute(
102107
step_context=build_step_context(config.fill_form_config),
103108
form_filename=agent_state.form_filename,
104-
form_title=agent_state.extracted_form_title,
105-
form_fields=agent_state.extracted_form_fields,
109+
form=agent_state.extracted_form,
106110
)
107111

108112
match result:
@@ -143,14 +147,16 @@ async def _handle_incomplete_result(context: ConversationContext, result: Incomp
143147
raise ValueError(f"Unexpected incomplete result type: {result}")
144148

145149

146-
async def _send_message(context: ConversationContext, message: str, debug: dict) -> None:
150+
async def _send_message(
151+
context: ConversationContext, message: str, debug: dict, message_type: MessageType = MessageType.chat
152+
) -> None:
147153
if not message:
148154
return
149155

150156
await context.send_messages(
151157
NewConversationMessage(
152158
content=message,
153-
message_type=MessageType.chat,
159+
message_type=message_type,
154160
debug_data=debug,
155161
)
156162
)

assistants/prospector-assistant/assistant/agents/form_fill_extension/state.py renamed to assistants/prospector-assistant/assistant/form_fill_extension/state.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,21 @@ class FormField(BaseModel):
3939
)
4040

4141

42+
class Section(BaseModel):
43+
title: str = Field(description="The title of the section if one is provided on the form.")
44+
description: str = Field(description="The description of the section if one is provided on the form.")
45+
instructions: str = Field(description="The instructions for the section if they are provided on the form.")
46+
fields: list[FormField] = Field(description="The fields of the section.")
47+
48+
49+
class Form(BaseModel):
50+
title: str = Field(description="The title of the form.")
51+
description: str = Field(description="The description of the form if one is provided on the form.")
52+
instructions: str = Field(description="The instructions for the form if they are provided on the form.")
53+
fields: list[FormField] = Field(description="The fields of the form, if there are any at the top level.")
54+
sections: list[Section] = Field(description="The sections of the form, if there are any.")
55+
56+
4257
class FormFillExtensionMode(StrEnum):
4358
acquire_form_step = "acquire_form"
4459
extract_form_fields = "extract_form_fields"
@@ -49,8 +64,7 @@ class FormFillExtensionMode(StrEnum):
4964
class FormFillExtensionState(BaseModel):
5065
mode: FormFillExtensionMode = FormFillExtensionMode.acquire_form_step
5166
form_filename: str = ""
52-
extracted_form_title: str = ""
53-
extracted_form_fields: list[FormField] = []
67+
extracted_form: Form | None = None
5468
populated_form_markdown: str = ""
5569
fill_form_gc_artifact: dict | None = None
5670

@@ -81,4 +95,4 @@ async def extension_state(context: ConversationContext) -> AsyncIterator[FormFil
8195
current_state.set(None)
8296

8397

84-
inspector = FileStateInspector(display_name="FormFill Agent", file_path_source=path_for_state)
98+
inspector = FileStateInspector(display_name="Debug: FormFill Agent", file_path_source=path_for_state)

0 commit comments

Comments
 (0)