hammad-python 0.0.23__py3-none-any.whl → 0.0.25__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hammad/__init__.py +62 -14
- hammad/_main.py +226 -0
- hammad/cli/__init__.py +0 -2
- hammad/cli/plugins.py +3 -1
- hammad/data/__init__.py +4 -5
- hammad/data/types/__init__.py +37 -1
- hammad/data/types/file.py +74 -1
- hammad/data/types/multimodal/__init__.py +14 -2
- hammad/data/types/multimodal/audio.py +106 -2
- hammad/data/types/multimodal/image.py +104 -2
- hammad/data/types/text.py +242 -0
- hammad/genai/__init__.py +73 -0
- hammad/genai/a2a/__init__.py +32 -0
- hammad/genai/a2a/workers.py +552 -0
- hammad/genai/agents/__init__.py +8 -0
- hammad/genai/agents/agent.py +747 -214
- hammad/genai/agents/run.py +421 -12
- hammad/genai/agents/types/agent_response.py +2 -1
- hammad/genai/graphs/__init__.py +125 -0
- hammad/genai/graphs/base.py +1786 -0
- hammad/genai/graphs/plugins.py +316 -0
- hammad/genai/graphs/types.py +638 -0
- hammad/genai/models/language/__init__.py +6 -1
- hammad/genai/models/language/model.py +46 -0
- hammad/genai/models/language/run.py +330 -4
- hammad/genai/models/language/types/language_model_response.py +1 -1
- hammad/genai/types/tools.py +1 -1
- hammad/logging/logger.py +60 -5
- hammad/mcp/__init__.py +3 -0
- hammad/types.py +288 -0
- {hammad_python-0.0.23.dist-info → hammad_python-0.0.25.dist-info}/METADATA +6 -1
- {hammad_python-0.0.23.dist-info → hammad_python-0.0.25.dist-info}/RECORD +34 -32
- hammad/_main/__init__.py +0 -4
- hammad/_main/_fn.py +0 -20
- hammad/_main/_new.py +0 -52
- hammad/_main/_run.py +0 -50
- hammad/_main/_to.py +0 -19
- hammad/cli/_runner.py +0 -265
- {hammad_python-0.0.23.dist-info → hammad_python-0.0.25.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.23.dist-info → hammad_python-0.0.25.dist-info}/licenses/LICENSE +0 -0
hammad/genai/agents/agent.py
CHANGED
@@ -11,6 +11,7 @@ from typing import (
|
|
11
11
|
Optional,
|
12
12
|
Union,
|
13
13
|
Dict,
|
14
|
+
TypeAlias,
|
14
15
|
overload,
|
15
16
|
TYPE_CHECKING,
|
16
17
|
)
|
@@ -49,7 +50,10 @@ from .types.agent_hooks import HookManager, HookDecorator
|
|
49
50
|
from .types.agent_messages import AgentMessages
|
50
51
|
|
51
52
|
if TYPE_CHECKING:
|
52
|
-
|
53
|
+
try:
|
54
|
+
from fasta2a import FastA2A
|
55
|
+
except ImportError:
|
56
|
+
FastA2A: TypeAlias = Any
|
53
57
|
|
54
58
|
|
55
59
|
T = TypeVar("T")
|
@@ -210,6 +214,13 @@ def _update_context_object(
|
|
210
214
|
raise ValueError(f"Cannot update context of type {type(context)}")
|
211
215
|
|
212
216
|
|
217
|
+
def mark_complete() -> None:
|
218
|
+
"""If you feel you are ready to respond to the user, or have completed
|
219
|
+
the task given to you, call this function to mark your response as
|
220
|
+
complete."""
|
221
|
+
return "complete"
|
222
|
+
|
223
|
+
|
213
224
|
class Agent(BaseGenAIModel, Generic[T]):
|
214
225
|
"""A generative AI agent that can execute tools, generate structured outputs,
|
215
226
|
and maintain context across multiple conversation steps.
|
@@ -245,6 +256,11 @@ class Agent(BaseGenAIModel, Generic[T]):
|
|
245
256
|
tools: Union[List[Tool], Callable, None] = None,
|
246
257
|
settings: Optional[AgentSettings] = None,
|
247
258
|
instructor_mode: Optional[LanguageModelInstructorMode] = None,
|
259
|
+
# Defaults
|
260
|
+
max_steps: int = 10,
|
261
|
+
# End Strategy
|
262
|
+
end_strategy: Literal["tool"] | None = None,
|
263
|
+
end_tool: Callable = mark_complete,
|
248
264
|
# Context management parameters
|
249
265
|
context_updates: Optional[
|
250
266
|
Union[List[Literal["before", "after"]], Literal["before", "after"]]
|
@@ -256,6 +272,8 @@ class Agent(BaseGenAIModel, Generic[T]):
|
|
256
272
|
context_selection_instructions: Optional[str] = None,
|
257
273
|
context_update_instructions: Optional[str] = None,
|
258
274
|
context_format: Literal["json", "python", "markdown"] = "json",
|
275
|
+
verbose: bool = False,
|
276
|
+
debug: bool = False,
|
259
277
|
**kwargs: Any,
|
260
278
|
):
|
261
279
|
"""Create a new AI agent with specified capabilities and behavior.
|
@@ -272,6 +290,11 @@ class Agent(BaseGenAIModel, Generic[T]):
|
|
272
290
|
tools: List of tools/functions the agent can call, or a single callable
|
273
291
|
settings: AgentSettings object to customize default behavior
|
274
292
|
instructor_mode: Mode for structured output generation
|
293
|
+
max_steps: Default ,aximum number of steps the agent can take before stopping
|
294
|
+
end_strategy: Optional alternative strategy to provide an end tool for determining agent's final
|
295
|
+
response.
|
296
|
+
end_tool: The tool the agent will call to determine if it should stop.
|
297
|
+
This is only used if end_strategy is set to "tool".
|
275
298
|
context_updates: When to update context - "before", "after", or both
|
276
299
|
context_confirm: Whether to confirm context updates with the user
|
277
300
|
context_strategy: How to select context updates - "selective" or "all"
|
@@ -280,6 +303,8 @@ class Agent(BaseGenAIModel, Generic[T]):
|
|
280
303
|
context_selection_instructions: Custom instructions for context selection
|
281
304
|
context_update_instructions: Custom instructions for context updates
|
282
305
|
context_format: Format for context display - "json", "python", or "markdown"
|
306
|
+
verbose: If True, set logger to INFO level for detailed output
|
307
|
+
debug: If True, set logger to DEBUG level for maximum verbosity
|
283
308
|
**kwargs: Additional parameters passed to the underlying language model
|
284
309
|
|
285
310
|
Example:
|
@@ -307,6 +332,17 @@ class Agent(BaseGenAIModel, Generic[T]):
|
|
307
332
|
self.settings = settings or AgentSettings()
|
308
333
|
self.instructor_mode = instructor_mode
|
309
334
|
|
335
|
+
# Store max_steps as instance variable (overrides settings if provided)
|
336
|
+
self.max_steps = max_steps if max_steps is not None else self.settings.max_steps
|
337
|
+
|
338
|
+
# Store end strategy parameters
|
339
|
+
self.end_strategy = end_strategy
|
340
|
+
self.end_tool = end_tool if end_tool is not None else mark_complete
|
341
|
+
|
342
|
+
# Add end_tool to tools if end_strategy is 'tool'
|
343
|
+
if self.end_strategy == "tool":
|
344
|
+
self.tools.append(define_tool(self.end_tool))
|
345
|
+
|
310
346
|
# Process instructions
|
311
347
|
self.instructions = _get_instructions(
|
312
348
|
name=name,
|
@@ -314,11 +350,23 @@ class Agent(BaseGenAIModel, Generic[T]):
|
|
314
350
|
add_name_to_instructions=self.settings.add_name_to_instructions,
|
315
351
|
)
|
316
352
|
|
353
|
+
# Store verbose/debug settings
|
354
|
+
self.verbose = verbose
|
355
|
+
self.debug = debug
|
356
|
+
|
357
|
+
# Set logger level based on verbose/debug flags
|
358
|
+
if debug:
|
359
|
+
logger.level = "debug"
|
360
|
+
elif verbose:
|
361
|
+
logger.level = "info"
|
362
|
+
|
317
363
|
# Initialize the language model
|
318
364
|
if isinstance(model, LanguageModel):
|
319
365
|
self._language_model = model
|
320
366
|
else:
|
321
|
-
self._language_model = LanguageModel(
|
367
|
+
self._language_model = LanguageModel(
|
368
|
+
model=model, verbose=verbose, debug=debug, **kwargs
|
369
|
+
)
|
322
370
|
|
323
371
|
# Context management settings
|
324
372
|
self.context_updates = context_updates
|
@@ -689,6 +737,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
689
737
|
max_steps: Optional[int] = None,
|
690
738
|
context: Optional[AgentContext] = None,
|
691
739
|
output_type: Optional[Type[T]] = None,
|
740
|
+
end_strategy: Optional[Literal["tool"]] = None,
|
741
|
+
end_tool: Optional[Callable] = None,
|
692
742
|
context_updates: Optional[
|
693
743
|
Union[List[Literal["before", "after"]], Literal["before", "after"]]
|
694
744
|
] = None,
|
@@ -699,6 +749,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
699
749
|
context_selection_instructions: Optional[str] = None,
|
700
750
|
context_update_instructions: Optional[str] = None,
|
701
751
|
context_format: Optional[Literal["json", "python", "markdown"]] = None,
|
752
|
+
verbose: Optional[bool] = None,
|
753
|
+
debug: Optional[bool] = None,
|
702
754
|
*,
|
703
755
|
stream: Literal[False] = False,
|
704
756
|
**kwargs: Any,
|
@@ -712,6 +764,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
712
764
|
max_steps: Optional[int] = None,
|
713
765
|
context: Optional[AgentContext] = None,
|
714
766
|
output_type: Optional[Type[T]] = None,
|
767
|
+
end_strategy: Optional[Literal["tool"]] = None,
|
768
|
+
end_tool: Optional[Callable] = None,
|
715
769
|
context_updates: Optional[
|
716
770
|
Union[List[Literal["before", "after"]], Literal["before", "after"]]
|
717
771
|
] = None,
|
@@ -722,6 +776,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
722
776
|
context_selection_instructions: Optional[str] = None,
|
723
777
|
context_update_instructions: Optional[str] = None,
|
724
778
|
context_format: Optional[Literal["json", "python", "markdown"]] = None,
|
779
|
+
verbose: Optional[bool] = None,
|
780
|
+
debug: Optional[bool] = None,
|
725
781
|
*,
|
726
782
|
stream: Literal[True],
|
727
783
|
**kwargs: Any,
|
@@ -734,6 +790,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
734
790
|
max_steps: Optional[int] = None,
|
735
791
|
context: Optional[AgentContext] = None,
|
736
792
|
output_type: Optional[Type[T]] = None,
|
793
|
+
end_strategy: Optional[Literal["tool"]] = None,
|
794
|
+
end_tool: Optional[Callable] = None,
|
737
795
|
context_updates: Optional[
|
738
796
|
Union[List[Literal["before", "after"]], Literal["before", "after"]]
|
739
797
|
] = None,
|
@@ -744,6 +802,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
744
802
|
context_selection_instructions: Optional[str] = None,
|
745
803
|
context_update_instructions: Optional[str] = None,
|
746
804
|
context_format: Optional[Literal["json", "python", "markdown"]] = None,
|
805
|
+
verbose: Optional[bool] = None,
|
806
|
+
debug: Optional[bool] = None,
|
747
807
|
stream: bool = False,
|
748
808
|
**kwargs: Any,
|
749
809
|
) -> Union[AgentResponse[T, AgentContext], AgentStream[T, AgentContext]]:
|
@@ -831,132 +891,321 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
831
891
|
**kwargs,
|
832
892
|
)
|
833
893
|
|
834
|
-
#
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
max_steps
|
845
|
-
|
846
|
-
# Get effective context settings
|
847
|
-
effective_context_settings = self._get_effective_context_settings(
|
848
|
-
context_updates=context_updates,
|
849
|
-
context_confirm=context_confirm,
|
850
|
-
context_strategy=context_strategy,
|
851
|
-
context_max_retries=context_max_retries,
|
852
|
-
context_confirm_instructions=context_confirm_instructions,
|
853
|
-
context_selection_instructions=context_selection_instructions,
|
854
|
-
context_update_instructions=context_update_instructions,
|
855
|
-
context_format=context_format,
|
894
|
+
# Set logger level for this request if specified
|
895
|
+
original_level = logger.level
|
896
|
+
if debug or (debug is None and self.debug):
|
897
|
+
logger.level = "debug"
|
898
|
+
elif verbose or (verbose is None and self.verbose):
|
899
|
+
logger.level = "info"
|
900
|
+
|
901
|
+
# Log agent execution start
|
902
|
+
logger.info(f"Starting agent '{self.name}' execution")
|
903
|
+
logger.debug(
|
904
|
+
f"Agent settings: max_steps={max_steps or self.max_steps}, tools={len(self.tools)}"
|
856
905
|
)
|
857
906
|
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
context, "before", effective_context_settings["context_updates"]
|
868
|
-
):
|
869
|
-
context = self._perform_context_update(
|
870
|
-
context=context,
|
871
|
-
model=working_model,
|
872
|
-
current_messages=current_messages,
|
873
|
-
timing="before",
|
874
|
-
effective_settings=effective_context_settings,
|
875
|
-
)
|
876
|
-
|
877
|
-
# Format messages with instructions and context for first step only
|
878
|
-
if step == 0:
|
879
|
-
formatted_messages = self._format_messages_with_context(
|
880
|
-
messages=current_messages,
|
881
|
-
context=context,
|
907
|
+
try:
|
908
|
+
# Use provided model or default
|
909
|
+
if model is None:
|
910
|
+
working_model = self.language_model
|
911
|
+
elif isinstance(model, str):
|
912
|
+
working_model = LanguageModel(
|
913
|
+
model=model,
|
914
|
+
verbose=verbose or self.verbose,
|
915
|
+
debug=debug or self.debug,
|
882
916
|
)
|
883
917
|
else:
|
884
|
-
|
885
|
-
|
886
|
-
# Prepare kwargs for language model
|
887
|
-
model_kwargs = kwargs.copy()
|
888
|
-
if output_type:
|
889
|
-
model_kwargs["type"] = output_type
|
890
|
-
if self.instructor_mode:
|
891
|
-
model_kwargs["instructor_mode"] = self.instructor_mode
|
892
|
-
|
893
|
-
# Get language model response
|
894
|
-
response = working_model.run(
|
895
|
-
messages=formatted_messages,
|
896
|
-
tools=[tool.to_dict() for tool in self.tools] if self.tools else None,
|
897
|
-
**model_kwargs,
|
898
|
-
)
|
918
|
+
working_model = model
|
899
919
|
|
900
|
-
#
|
901
|
-
if
|
902
|
-
|
903
|
-
current_messages.append(response.to_message())
|
920
|
+
# Use provided max_steps or default from instance
|
921
|
+
if max_steps is None:
|
922
|
+
max_steps = self.max_steps
|
904
923
|
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
924
|
+
# Use provided end_strategy or default from instance
|
925
|
+
effective_end_strategy = (
|
926
|
+
end_strategy if end_strategy is not None else self.end_strategy
|
927
|
+
)
|
928
|
+
effective_end_tool = end_tool if end_tool is not None else self.end_tool
|
929
|
+
|
930
|
+
# Create working tools list with end_tool if needed
|
931
|
+
working_tools = self.tools.copy()
|
932
|
+
if effective_end_strategy == "tool" and effective_end_tool is not None:
|
933
|
+
end_tool_obj = define_tool(effective_end_tool)
|
934
|
+
# Only add if not already present
|
935
|
+
if not any(tool.name == end_tool_obj.name for tool in working_tools):
|
936
|
+
working_tools.append(end_tool_obj)
|
937
|
+
|
938
|
+
# Get effective context settings
|
939
|
+
effective_context_settings = self._get_effective_context_settings(
|
940
|
+
context_updates=context_updates,
|
941
|
+
context_confirm=context_confirm,
|
942
|
+
context_strategy=context_strategy,
|
943
|
+
context_max_retries=context_max_retries,
|
944
|
+
context_confirm_instructions=context_confirm_instructions,
|
945
|
+
context_selection_instructions=context_selection_instructions,
|
946
|
+
context_update_instructions=context_update_instructions,
|
947
|
+
context_format=context_format,
|
948
|
+
)
|
912
949
|
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
950
|
+
# Parse initial messages
|
951
|
+
parsed_messages = parse_messages(messages)
|
952
|
+
current_messages = parsed_messages.copy()
|
953
|
+
steps: List[LanguageModelResponse[str]] = []
|
954
|
+
|
955
|
+
# RUN MAIN AGENTIC LOOP
|
956
|
+
logger.debug(f"Starting agentic loop with max_steps={max_steps}")
|
957
|
+
for step in range(max_steps):
|
958
|
+
logger.debug(f"Agent step {step + 1}/{max_steps}")
|
959
|
+
# Update context before processing if configured
|
918
960
|
if context and self._should_update_context(
|
919
|
-
context, "
|
961
|
+
context, "before", effective_context_settings["context_updates"]
|
920
962
|
):
|
921
963
|
context = self._perform_context_update(
|
922
964
|
context=context,
|
923
965
|
model=working_model,
|
924
966
|
current_messages=current_messages,
|
925
|
-
timing="
|
967
|
+
timing="before",
|
926
968
|
effective_settings=effective_context_settings,
|
927
969
|
)
|
928
|
-
|
929
|
-
|
970
|
+
|
971
|
+
# Format messages with instructions and context for first step only
|
972
|
+
if step == 0:
|
973
|
+
formatted_messages = self._format_messages_with_context(
|
974
|
+
messages=current_messages,
|
975
|
+
context=context,
|
976
|
+
)
|
977
|
+
else:
|
978
|
+
formatted_messages = current_messages
|
979
|
+
|
980
|
+
# Prepare kwargs for language model
|
981
|
+
model_kwargs = kwargs.copy()
|
982
|
+
# Don't add output_type for intermediate steps - only for final response
|
983
|
+
if self.instructor_mode:
|
984
|
+
model_kwargs["instructor_mode"] = self.instructor_mode
|
985
|
+
|
986
|
+
# Get language model response
|
987
|
+
response = working_model.run(
|
988
|
+
messages=formatted_messages,
|
989
|
+
tools=[tool.to_dict() for tool in working_tools]
|
990
|
+
if working_tools
|
991
|
+
else None,
|
992
|
+
**model_kwargs,
|
930
993
|
)
|
931
994
|
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
995
|
+
# Check if response has tool calls
|
996
|
+
if response.has_tool_calls():
|
997
|
+
logger.info(
|
998
|
+
f"Agent '{self.name}' making tool calls: {len(response.tool_calls)} tools"
|
999
|
+
)
|
1000
|
+
for tool_call in response.tool_calls:
|
1001
|
+
logger.debug(
|
1002
|
+
f"Tool call: {tool_call.function.name}({tool_call.function.arguments})"
|
1003
|
+
)
|
1004
|
+
|
1005
|
+
# Add response to message history (with tool calls)
|
1006
|
+
current_messages.append(response.to_message())
|
1007
|
+
|
1008
|
+
# Execute tools and add their responses to messages
|
1009
|
+
tool_responses = execute_tools_from_language_model_response(
|
1010
|
+
tools=working_tools, response=response
|
1011
|
+
)
|
1012
|
+
# Add tool responses to message history
|
1013
|
+
for tool_resp in tool_responses:
|
1014
|
+
current_messages.append(tool_resp.to_dict())
|
1015
|
+
|
1016
|
+
# This is not the final step, add to steps
|
1017
|
+
steps.append(response)
|
1018
|
+
else:
|
1019
|
+
# No tool calls - check if this is actually the final step based on end_strategy
|
1020
|
+
if effective_end_strategy == "tool":
|
1021
|
+
# Check if the end_tool was called
|
1022
|
+
end_tool_called = (
|
1023
|
+
any(
|
1024
|
+
tool_call.function.name == effective_end_tool.__name__
|
1025
|
+
for tool_call in response.tool_calls
|
1026
|
+
)
|
1027
|
+
if response.tool_calls
|
1028
|
+
else False
|
1029
|
+
)
|
1030
|
+
|
1031
|
+
if not end_tool_called:
|
1032
|
+
# End tool not called, continue the conversation
|
1033
|
+
logger.debug(
|
1034
|
+
f"Agent '{self.name}' step {step + 1}: No end tool called, continuing..."
|
1035
|
+
)
|
1036
|
+
|
1037
|
+
# Add the response to history
|
1038
|
+
current_messages.append(response.to_message())
|
1039
|
+
|
1040
|
+
# Add system message instructing agent to call the end tool
|
1041
|
+
current_messages.append(
|
1042
|
+
{
|
1043
|
+
"role": "system",
|
1044
|
+
"content": f"You must call the {effective_end_tool.__name__} tool to complete your response. Do not provide a final answer until you have called this tool.",
|
1045
|
+
}
|
1046
|
+
)
|
1047
|
+
|
1048
|
+
# Add user message to continue
|
1049
|
+
current_messages.append(
|
1050
|
+
{"role": "user", "content": "continue"}
|
1051
|
+
)
|
1052
|
+
|
1053
|
+
# Remove the continue message and append assistant content
|
1054
|
+
current_messages.pop() # Remove "continue" message
|
1055
|
+
|
1056
|
+
# This is not the final step, add to steps and continue
|
1057
|
+
steps.append(response)
|
1058
|
+
continue
|
1059
|
+
|
1060
|
+
# This is the final step (either no end_strategy or end_tool was called)
|
1061
|
+
logger.info(
|
1062
|
+
f"Agent '{self.name}' completed execution in {step + 1} steps"
|
1063
|
+
)
|
1064
|
+
# Now we can make the final call with the output_type if specified
|
1065
|
+
# Only make structured output call for non-str types
|
1066
|
+
if output_type and output_type != str:
|
1067
|
+
# Make a final call with the structured output type
|
1068
|
+
final_model_kwargs = kwargs.copy()
|
1069
|
+
final_model_kwargs["type"] = output_type
|
1070
|
+
if self.instructor_mode:
|
1071
|
+
final_model_kwargs["instructor_mode"] = self.instructor_mode
|
1072
|
+
|
1073
|
+
# Create a clean conversation history for structured output
|
1074
|
+
# Include the original messages and the final response content
|
1075
|
+
clean_messages = []
|
1076
|
+
# Add original user messages (excluding tool calls/responses)
|
1077
|
+
for msg in formatted_messages:
|
1078
|
+
if isinstance(msg, dict) and msg.get("role") not in [
|
1079
|
+
"tool",
|
1080
|
+
"assistant",
|
1081
|
+
]:
|
1082
|
+
clean_messages.append(msg)
|
1083
|
+
elif hasattr(msg, "role") and msg.role not in [
|
1084
|
+
"tool",
|
1085
|
+
"assistant",
|
1086
|
+
]:
|
1087
|
+
clean_messages.append(msg.to_dict())
|
1088
|
+
|
1089
|
+
# Add the final assistant response content
|
1090
|
+
clean_messages.append(
|
1091
|
+
{"role": "assistant", "content": response.get_content()}
|
1092
|
+
)
|
1093
|
+
|
1094
|
+
# Use the clean conversation history to generate structured output
|
1095
|
+
final_response = working_model.run(
|
1096
|
+
messages=clean_messages,
|
1097
|
+
**final_model_kwargs,
|
1098
|
+
)
|
1099
|
+
|
1100
|
+
# Update context after processing if configured
|
1101
|
+
if context and self._should_update_context(
|
1102
|
+
context,
|
1103
|
+
"after",
|
1104
|
+
effective_context_settings["context_updates"],
|
1105
|
+
):
|
1106
|
+
context = self._perform_context_update(
|
1107
|
+
context=context,
|
1108
|
+
model=working_model,
|
1109
|
+
current_messages=current_messages,
|
1110
|
+
timing="after",
|
1111
|
+
effective_settings=effective_context_settings,
|
1112
|
+
)
|
1113
|
+
return _create_agent_response_from_language_model_response(
|
1114
|
+
response=final_response, steps=steps, context=context
|
1115
|
+
)
|
1116
|
+
else:
|
1117
|
+
# Update context after processing if configured
|
1118
|
+
if context and self._should_update_context(
|
1119
|
+
context,
|
1120
|
+
"after",
|
1121
|
+
effective_context_settings["context_updates"],
|
1122
|
+
):
|
1123
|
+
context = self._perform_context_update(
|
1124
|
+
context=context,
|
1125
|
+
model=working_model,
|
1126
|
+
current_messages=current_messages,
|
1127
|
+
timing="after",
|
1128
|
+
effective_settings=effective_context_settings,
|
1129
|
+
)
|
1130
|
+
return _create_agent_response_from_language_model_response(
|
1131
|
+
response=response, steps=steps, context=context
|
1132
|
+
)
|
1133
|
+
|
1134
|
+
# Max steps reached - return last response
|
1135
|
+
if steps:
|
1136
|
+
final_response = steps[-1]
|
1137
|
+
# If we have an output_type, make a final structured call (but not for str)
|
1138
|
+
if output_type and output_type != str:
|
1139
|
+
final_model_kwargs = kwargs.copy()
|
1140
|
+
final_model_kwargs["type"] = output_type
|
1141
|
+
if self.instructor_mode:
|
1142
|
+
final_model_kwargs["instructor_mode"] = self.instructor_mode
|
1143
|
+
|
1144
|
+
# Create clean messages for structured output
|
1145
|
+
clean_messages = []
|
1146
|
+
formatted_messages = self._format_messages_with_context(
|
1147
|
+
messages=current_messages,
|
1148
|
+
context=context,
|
1149
|
+
)
|
1150
|
+
|
1151
|
+
# Add original user messages (excluding tool calls/responses)
|
1152
|
+
for msg in formatted_messages:
|
1153
|
+
if isinstance(msg, dict) and msg.get("role") not in [
|
1154
|
+
"tool",
|
1155
|
+
"assistant",
|
1156
|
+
]:
|
1157
|
+
clean_messages.append(msg)
|
1158
|
+
elif hasattr(msg, "role") and msg.role not in [
|
1159
|
+
"tool",
|
1160
|
+
"assistant",
|
1161
|
+
]:
|
1162
|
+
clean_messages.append(msg.to_dict())
|
1163
|
+
|
1164
|
+
# Add final response content
|
1165
|
+
clean_messages.append(
|
1166
|
+
{"role": "assistant", "content": final_response.get_content()}
|
1167
|
+
)
|
1168
|
+
|
1169
|
+
final_response = working_model.run(
|
1170
|
+
messages=clean_messages,
|
1171
|
+
**final_model_kwargs,
|
1172
|
+
)
|
1173
|
+
else:
|
1174
|
+
# No steps taken, make a final call
|
1175
|
+
final_model_kwargs = kwargs.copy()
|
1176
|
+
if output_type and output_type != str:
|
1177
|
+
final_model_kwargs["type"] = output_type
|
1178
|
+
if self.instructor_mode:
|
1179
|
+
final_model_kwargs["instructor_mode"] = self.instructor_mode
|
1180
|
+
|
1181
|
+
final_response = working_model.run(
|
1182
|
+
messages=self._format_messages_with_context(
|
1183
|
+
messages=current_messages,
|
1184
|
+
context=context,
|
1185
|
+
),
|
1186
|
+
**final_model_kwargs,
|
1187
|
+
)
|
1188
|
+
|
1189
|
+
# Update context after processing if configured
|
1190
|
+
if context and self._should_update_context(
|
1191
|
+
context, "after", effective_context_settings["context_updates"]
|
1192
|
+
):
|
1193
|
+
context = self._perform_context_update(
|
940
1194
|
context=context,
|
941
|
-
|
942
|
-
|
943
|
-
|
1195
|
+
model=working_model,
|
1196
|
+
current_messages=current_messages,
|
1197
|
+
timing="after",
|
1198
|
+
effective_settings=effective_context_settings,
|
1199
|
+
)
|
944
1200
|
|
945
|
-
|
946
|
-
|
947
|
-
context, "after", effective_context_settings["context_updates"]
|
948
|
-
):
|
949
|
-
context = self._perform_context_update(
|
950
|
-
context=context,
|
951
|
-
model=working_model,
|
952
|
-
current_messages=current_messages,
|
953
|
-
timing="after",
|
954
|
-
effective_settings=effective_context_settings,
|
1201
|
+
return _create_agent_response_from_language_model_response(
|
1202
|
+
response=final_response, steps=steps, context=context
|
955
1203
|
)
|
956
1204
|
|
957
|
-
|
958
|
-
|
959
|
-
|
1205
|
+
finally:
|
1206
|
+
# Restore original logger level
|
1207
|
+
if debug is not None or verbose is not None:
|
1208
|
+
logger.level = original_level
|
960
1209
|
|
961
1210
|
async def async_run(
|
962
1211
|
self,
|
@@ -975,6 +1224,10 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
975
1224
|
context_selection_instructions: Optional[str] = None,
|
976
1225
|
context_update_instructions: Optional[str] = None,
|
977
1226
|
context_format: Optional[Literal["json", "python", "markdown"]] = None,
|
1227
|
+
verbose: Optional[bool] = None,
|
1228
|
+
debug: Optional[bool] = None,
|
1229
|
+
end_strategy: Optional[Literal["tool"]] = None,
|
1230
|
+
end_tool: Optional[Callable] = None,
|
978
1231
|
**kwargs: Any,
|
979
1232
|
) -> AgentResponse[T, AgentContext]:
|
980
1233
|
"""Runs this agent asynchronously and returns a final agent response.
|
@@ -1044,132 +1297,302 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
1044
1297
|
... )
|
1045
1298
|
... return response.output
|
1046
1299
|
"""
|
1047
|
-
#
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
context_strategy=context_strategy,
|
1064
|
-
context_max_retries=context_max_retries,
|
1065
|
-
context_confirm_instructions=context_confirm_instructions,
|
1066
|
-
context_selection_instructions=context_selection_instructions,
|
1067
|
-
context_update_instructions=context_update_instructions,
|
1068
|
-
context_format=context_format,
|
1069
|
-
)
|
1070
|
-
|
1071
|
-
# Parse initial messages
|
1072
|
-
parsed_messages = parse_messages(messages)
|
1073
|
-
current_messages = parsed_messages.copy()
|
1074
|
-
steps: List[LanguageModelResponse[str]] = []
|
1075
|
-
|
1076
|
-
# RUN MAIN AGENTIC LOOP
|
1077
|
-
for step in range(max_steps):
|
1078
|
-
# Update context before processing if configured
|
1079
|
-
if context and self._should_update_context(
|
1080
|
-
context, "before", effective_context_settings["context_updates"]
|
1081
|
-
):
|
1082
|
-
context = self._perform_context_update(
|
1083
|
-
context=context,
|
1084
|
-
model=working_model,
|
1085
|
-
current_messages=current_messages,
|
1086
|
-
timing="before",
|
1087
|
-
effective_settings=effective_context_settings,
|
1088
|
-
)
|
1089
|
-
|
1090
|
-
# Format messages with instructions and context for first step only
|
1091
|
-
if step == 0:
|
1092
|
-
formatted_messages = self._format_messages_with_context(
|
1093
|
-
messages=current_messages,
|
1094
|
-
context=context,
|
1300
|
+
# Set logger level for this request if specified
|
1301
|
+
original_level = logger.level
|
1302
|
+
if debug or (debug is None and self.debug):
|
1303
|
+
logger.level = "debug"
|
1304
|
+
elif verbose or (verbose is None and self.verbose):
|
1305
|
+
logger.level = "info"
|
1306
|
+
|
1307
|
+
try:
|
1308
|
+
# Use provided model or default
|
1309
|
+
if model is None:
|
1310
|
+
working_model = self.language_model
|
1311
|
+
elif isinstance(model, str):
|
1312
|
+
working_model = LanguageModel(
|
1313
|
+
model=model,
|
1314
|
+
verbose=verbose or self.verbose,
|
1315
|
+
debug=debug or self.debug,
|
1095
1316
|
)
|
1096
1317
|
else:
|
1097
|
-
|
1098
|
-
|
1099
|
-
# Prepare kwargs for language model
|
1100
|
-
model_kwargs = kwargs.copy()
|
1101
|
-
if output_type:
|
1102
|
-
model_kwargs["type"] = output_type
|
1103
|
-
if self.instructor_mode:
|
1104
|
-
model_kwargs["instructor_mode"] = self.instructor_mode
|
1105
|
-
|
1106
|
-
# Get language model response
|
1107
|
-
response = await working_model.async_run(
|
1108
|
-
messages=formatted_messages,
|
1109
|
-
tools=[tool.to_dict() for tool in self.tools] if self.tools else None,
|
1110
|
-
**model_kwargs,
|
1111
|
-
)
|
1318
|
+
working_model = model
|
1112
1319
|
|
1113
|
-
#
|
1114
|
-
if
|
1115
|
-
|
1116
|
-
current_messages.append(response.to_message())
|
1320
|
+
# Use provided max_steps or default from instance
|
1321
|
+
if max_steps is None:
|
1322
|
+
max_steps = self.max_steps
|
1117
1323
|
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1324
|
+
# Use provided end_strategy or default from instance
|
1325
|
+
effective_end_strategy = (
|
1326
|
+
end_strategy if end_strategy is not None else self.end_strategy
|
1327
|
+
)
|
1328
|
+
effective_end_tool = end_tool if end_tool is not None else self.end_tool
|
1329
|
+
|
1330
|
+
# Create working tools list with end_tool if needed
|
1331
|
+
working_tools = self.tools.copy()
|
1332
|
+
if effective_end_strategy == "tool" and effective_end_tool is not None:
|
1333
|
+
end_tool_obj = define_tool(effective_end_tool)
|
1334
|
+
# Only add if not already present
|
1335
|
+
if not any(tool.name == end_tool_obj.name for tool in working_tools):
|
1336
|
+
working_tools.append(end_tool_obj)
|
1337
|
+
|
1338
|
+
# Get effective context settings
|
1339
|
+
effective_context_settings = self._get_effective_context_settings(
|
1340
|
+
context_updates=context_updates,
|
1341
|
+
context_confirm=context_confirm,
|
1342
|
+
context_strategy=context_strategy,
|
1343
|
+
context_max_retries=context_max_retries,
|
1344
|
+
context_confirm_instructions=context_confirm_instructions,
|
1345
|
+
context_selection_instructions=context_selection_instructions,
|
1346
|
+
context_update_instructions=context_update_instructions,
|
1347
|
+
context_format=context_format,
|
1348
|
+
)
|
1125
1349
|
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1350
|
+
# Parse initial messages
|
1351
|
+
parsed_messages = parse_messages(messages)
|
1352
|
+
current_messages = parsed_messages.copy()
|
1353
|
+
steps: List[LanguageModelResponse[str]] = []
|
1354
|
+
|
1355
|
+
# RUN MAIN AGENTIC LOOP
|
1356
|
+
for step in range(max_steps):
|
1357
|
+
# Update context before processing if configured
|
1131
1358
|
if context and self._should_update_context(
|
1132
|
-
context, "
|
1359
|
+
context, "before", effective_context_settings["context_updates"]
|
1133
1360
|
):
|
1134
1361
|
context = self._perform_context_update(
|
1135
1362
|
context=context,
|
1136
1363
|
model=working_model,
|
1137
1364
|
current_messages=current_messages,
|
1138
|
-
timing="
|
1365
|
+
timing="before",
|
1139
1366
|
effective_settings=effective_context_settings,
|
1140
1367
|
)
|
1141
|
-
|
1142
|
-
|
1368
|
+
|
1369
|
+
# Format messages with instructions and context for first step only
|
1370
|
+
if step == 0:
|
1371
|
+
formatted_messages = self._format_messages_with_context(
|
1372
|
+
messages=current_messages,
|
1373
|
+
context=context,
|
1374
|
+
)
|
1375
|
+
else:
|
1376
|
+
formatted_messages = current_messages
|
1377
|
+
|
1378
|
+
# Prepare kwargs for language model
|
1379
|
+
model_kwargs = kwargs.copy()
|
1380
|
+
# Don't add output_type for intermediate steps - only for final response
|
1381
|
+
if self.instructor_mode:
|
1382
|
+
model_kwargs["instructor_mode"] = self.instructor_mode
|
1383
|
+
|
1384
|
+
# Get language model response
|
1385
|
+
response = await working_model.async_run(
|
1386
|
+
messages=formatted_messages,
|
1387
|
+
tools=[tool.to_dict() for tool in working_tools]
|
1388
|
+
if working_tools
|
1389
|
+
else None,
|
1390
|
+
**model_kwargs,
|
1143
1391
|
)
|
1144
1392
|
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1393
|
+
# Check if response has tool calls
|
1394
|
+
if response.has_tool_calls():
|
1395
|
+
# Add response to message history (with tool calls)
|
1396
|
+
current_messages.append(response.to_message())
|
1397
|
+
|
1398
|
+
# Execute tools and add their responses to messages
|
1399
|
+
tool_responses = execute_tools_from_language_model_response(
|
1400
|
+
tools=working_tools, response=response
|
1401
|
+
)
|
1402
|
+
# Add tool responses to message history
|
1403
|
+
for tool_resp in tool_responses:
|
1404
|
+
current_messages.append(tool_resp.to_dict())
|
1405
|
+
|
1406
|
+
# This is not the final step, add to steps
|
1407
|
+
steps.append(response)
|
1408
|
+
else:
|
1409
|
+
# No tool calls - check if this is actually the final step based on end_strategy
|
1410
|
+
if effective_end_strategy == "tool":
|
1411
|
+
# Check if the end_tool was called
|
1412
|
+
end_tool_called = (
|
1413
|
+
any(
|
1414
|
+
tool_call.function.name == effective_end_tool.__name__
|
1415
|
+
for tool_call in response.tool_calls
|
1416
|
+
)
|
1417
|
+
if response.tool_calls
|
1418
|
+
else False
|
1419
|
+
)
|
1420
|
+
|
1421
|
+
if not end_tool_called:
|
1422
|
+
# End tool not called, continue the conversation
|
1423
|
+
logger.debug(
|
1424
|
+
f"Agent '{self.name}' step {step + 1}: No end tool called, continuing..."
|
1425
|
+
)
|
1426
|
+
|
1427
|
+
# Add the response to history
|
1428
|
+
current_messages.append(response.to_message())
|
1429
|
+
|
1430
|
+
# Add system message instructing agent to call the end tool
|
1431
|
+
current_messages.append(
|
1432
|
+
{
|
1433
|
+
"role": "system",
|
1434
|
+
"content": f"You must call the {effective_end_tool.__name__} tool to complete your response. Do not provide a final answer until you have called this tool.",
|
1435
|
+
}
|
1436
|
+
)
|
1437
|
+
|
1438
|
+
# Add user message to continue
|
1439
|
+
current_messages.append(
|
1440
|
+
{"role": "user", "content": "continue"}
|
1441
|
+
)
|
1442
|
+
|
1443
|
+
# Remove the continue message and append assistant content
|
1444
|
+
current_messages.pop() # Remove "continue" message
|
1445
|
+
|
1446
|
+
# This is not the final step, add to steps and continue
|
1447
|
+
steps.append(response)
|
1448
|
+
continue
|
1449
|
+
|
1450
|
+
# This is the final step (either no end_strategy or end_tool was called)
|
1451
|
+
# Now we can make the final call with the output_type if specified
|
1452
|
+
# Only make structured output call for non-str types
|
1453
|
+
if output_type and output_type != str:
|
1454
|
+
# Make a final call with the structured output type
|
1455
|
+
final_model_kwargs = kwargs.copy()
|
1456
|
+
final_model_kwargs["type"] = output_type
|
1457
|
+
if self.instructor_mode:
|
1458
|
+
final_model_kwargs["instructor_mode"] = self.instructor_mode
|
1459
|
+
|
1460
|
+
# Create a clean conversation history for structured output
|
1461
|
+
# Include the original messages and the final response content
|
1462
|
+
clean_messages = []
|
1463
|
+
# Add original user messages (excluding tool calls/responses)
|
1464
|
+
for msg in formatted_messages:
|
1465
|
+
if isinstance(msg, dict) and msg.get("role") not in [
|
1466
|
+
"tool",
|
1467
|
+
"assistant",
|
1468
|
+
]:
|
1469
|
+
clean_messages.append(msg)
|
1470
|
+
elif hasattr(msg, "role") and msg.role not in [
|
1471
|
+
"tool",
|
1472
|
+
"assistant",
|
1473
|
+
]:
|
1474
|
+
clean_messages.append(msg.to_dict())
|
1475
|
+
|
1476
|
+
# Add the final assistant response content
|
1477
|
+
clean_messages.append(
|
1478
|
+
{"role": "assistant", "content": response.get_content()}
|
1479
|
+
)
|
1480
|
+
|
1481
|
+
# Use the clean conversation history to generate structured output
|
1482
|
+
final_response = await working_model.async_run(
|
1483
|
+
messages=clean_messages,
|
1484
|
+
**final_model_kwargs,
|
1485
|
+
)
|
1486
|
+
|
1487
|
+
# Update context after processing if configured
|
1488
|
+
if context and self._should_update_context(
|
1489
|
+
context,
|
1490
|
+
"after",
|
1491
|
+
effective_context_settings["context_updates"],
|
1492
|
+
):
|
1493
|
+
context = self._perform_context_update(
|
1494
|
+
context=context,
|
1495
|
+
model=working_model,
|
1496
|
+
current_messages=current_messages,
|
1497
|
+
timing="after",
|
1498
|
+
effective_settings=effective_context_settings,
|
1499
|
+
)
|
1500
|
+
return _create_agent_response_from_language_model_response(
|
1501
|
+
response=final_response, steps=steps, context=context
|
1502
|
+
)
|
1503
|
+
else:
|
1504
|
+
# Update context after processing if configured
|
1505
|
+
if context and self._should_update_context(
|
1506
|
+
context,
|
1507
|
+
"after",
|
1508
|
+
effective_context_settings["context_updates"],
|
1509
|
+
):
|
1510
|
+
context = self._perform_context_update(
|
1511
|
+
context=context,
|
1512
|
+
model=working_model,
|
1513
|
+
current_messages=current_messages,
|
1514
|
+
timing="after",
|
1515
|
+
effective_settings=effective_context_settings,
|
1516
|
+
)
|
1517
|
+
return _create_agent_response_from_language_model_response(
|
1518
|
+
response=response, steps=steps, context=context
|
1519
|
+
)
|
1520
|
+
|
1521
|
+
# Max steps reached - return last response
|
1522
|
+
if steps:
|
1523
|
+
final_response = steps[-1]
|
1524
|
+
# If we have an output_type, make a final structured call (but not for str)
|
1525
|
+
if output_type and output_type != str:
|
1526
|
+
final_model_kwargs = kwargs.copy()
|
1527
|
+
final_model_kwargs["type"] = output_type
|
1528
|
+
if self.instructor_mode:
|
1529
|
+
final_model_kwargs["instructor_mode"] = self.instructor_mode
|
1530
|
+
|
1531
|
+
# Create clean messages for structured output
|
1532
|
+
clean_messages = []
|
1533
|
+
formatted_messages = self._format_messages_with_context(
|
1534
|
+
messages=current_messages,
|
1535
|
+
context=context,
|
1536
|
+
)
|
1537
|
+
|
1538
|
+
# Add original user messages (excluding tool calls/responses)
|
1539
|
+
for msg in formatted_messages:
|
1540
|
+
if isinstance(msg, dict) and msg.get("role") not in [
|
1541
|
+
"tool",
|
1542
|
+
"assistant",
|
1543
|
+
]:
|
1544
|
+
clean_messages.append(msg)
|
1545
|
+
elif hasattr(msg, "role") and msg.role not in [
|
1546
|
+
"tool",
|
1547
|
+
"assistant",
|
1548
|
+
]:
|
1549
|
+
clean_messages.append(msg.to_dict())
|
1550
|
+
|
1551
|
+
# Add final response content
|
1552
|
+
clean_messages.append(
|
1553
|
+
{"role": "assistant", "content": final_response.get_content()}
|
1554
|
+
)
|
1555
|
+
|
1556
|
+
final_response = await working_model.async_run(
|
1557
|
+
messages=clean_messages,
|
1558
|
+
**final_model_kwargs,
|
1559
|
+
)
|
1560
|
+
else:
|
1561
|
+
# No steps taken, make a final call
|
1562
|
+
final_model_kwargs = kwargs.copy()
|
1563
|
+
if output_type and output_type != str:
|
1564
|
+
final_model_kwargs["type"] = output_type
|
1565
|
+
if self.instructor_mode:
|
1566
|
+
final_model_kwargs["instructor_mode"] = self.instructor_mode
|
1567
|
+
|
1568
|
+
final_response = await working_model.async_run(
|
1569
|
+
messages=self._format_messages_with_context(
|
1570
|
+
messages=current_messages,
|
1571
|
+
context=context,
|
1572
|
+
),
|
1573
|
+
**final_model_kwargs,
|
1574
|
+
)
|
1575
|
+
|
1576
|
+
# Update context after processing if configured
|
1577
|
+
if context and self._should_update_context(
|
1578
|
+
context, "after", effective_context_settings["context_updates"]
|
1579
|
+
):
|
1580
|
+
context = self._perform_context_update(
|
1153
1581
|
context=context,
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1582
|
+
model=working_model,
|
1583
|
+
current_messages=current_messages,
|
1584
|
+
timing="after",
|
1585
|
+
effective_settings=effective_context_settings,
|
1586
|
+
)
|
1157
1587
|
|
1158
|
-
|
1159
|
-
|
1160
|
-
context, "after", effective_context_settings["context_updates"]
|
1161
|
-
):
|
1162
|
-
context = self._perform_context_update(
|
1163
|
-
context=context,
|
1164
|
-
model=working_model,
|
1165
|
-
current_messages=current_messages,
|
1166
|
-
timing="after",
|
1167
|
-
effective_settings=effective_context_settings,
|
1588
|
+
return _create_agent_response_from_language_model_response(
|
1589
|
+
response=final_response, steps=steps, context=context
|
1168
1590
|
)
|
1169
1591
|
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1592
|
+
finally:
|
1593
|
+
# Restore original logger level
|
1594
|
+
if debug is not None or verbose is not None:
|
1595
|
+
logger.level = original_level
|
1173
1596
|
|
1174
1597
|
def stream(
|
1175
1598
|
self,
|
@@ -1204,6 +1627,106 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
1204
1627
|
**kwargs,
|
1205
1628
|
)
|
1206
1629
|
|
1630
|
+
def as_a2a(
|
1631
|
+
self,
|
1632
|
+
*,
|
1633
|
+
# Worker configuration
|
1634
|
+
context: Optional[AgentContext] = None,
|
1635
|
+
# Storage and broker configuration
|
1636
|
+
storage: Optional[Any] = None,
|
1637
|
+
broker: Optional[Any] = None,
|
1638
|
+
# Server configuration
|
1639
|
+
host: str = "0.0.0.0",
|
1640
|
+
port: int = 8000,
|
1641
|
+
reload: bool = False,
|
1642
|
+
workers: int = 1,
|
1643
|
+
log_level: str = "info",
|
1644
|
+
# A2A configuration
|
1645
|
+
name: Optional[str] = None,
|
1646
|
+
url: Optional[str] = None,
|
1647
|
+
version: str = "1.0.0",
|
1648
|
+
description: Optional[str] = None,
|
1649
|
+
# Advanced configuration
|
1650
|
+
lifespan_timeout: int = 30,
|
1651
|
+
**uvicorn_kwargs: Any,
|
1652
|
+
) -> "FastA2A": # type: ignore
|
1653
|
+
"""
|
1654
|
+
Convert this agent to an A2A server application.
|
1655
|
+
|
1656
|
+
This method creates a FastA2A server that can handle A2A requests
|
1657
|
+
for this agent instance. It sets up the necessary Worker, Storage,
|
1658
|
+
and Broker components automatically.
|
1659
|
+
|
1660
|
+
Args:
|
1661
|
+
context: Initial context for the agent
|
1662
|
+
storage: Custom storage backend (defaults to InMemoryStorage)
|
1663
|
+
broker: Custom broker backend (defaults to InMemoryBroker)
|
1664
|
+
host: Host to bind the server to
|
1665
|
+
port: Port to bind the server to
|
1666
|
+
reload: Enable auto-reload for development
|
1667
|
+
workers: Number of worker processes
|
1668
|
+
log_level: Logging level
|
1669
|
+
name: Agent name for the A2A server (defaults to agent's name)
|
1670
|
+
url: URL where the agent is hosted
|
1671
|
+
version: API version
|
1672
|
+
description: API description for the A2A server (defaults to agent's description)
|
1673
|
+
lifespan_timeout: Timeout for lifespan events
|
1674
|
+
**uvicorn_kwargs: Additional arguments passed to uvicorn
|
1675
|
+
|
1676
|
+
Returns:
|
1677
|
+
FastA2A application instance that can be run with uvicorn
|
1678
|
+
|
1679
|
+
Examples:
|
1680
|
+
Convert agent to A2A server:
|
1681
|
+
```python
|
1682
|
+
agent = Agent(
|
1683
|
+
name="assistant",
|
1684
|
+
instructions="You are a helpful assistant",
|
1685
|
+
model="openai/gpt-4o-mini"
|
1686
|
+
)
|
1687
|
+
|
1688
|
+
app = agent.as_a2a(port=8080)
|
1689
|
+
|
1690
|
+
# Run with uvicorn
|
1691
|
+
import uvicorn
|
1692
|
+
uvicorn.run(app, host="0.0.0.0", port=8080)
|
1693
|
+
```
|
1694
|
+
|
1695
|
+
Or use the CLI:
|
1696
|
+
```bash
|
1697
|
+
uvicorn mymodule:agent.as_a2a() --reload
|
1698
|
+
```
|
1699
|
+
|
1700
|
+
With custom configuration:
|
1701
|
+
```python
|
1702
|
+
app = agent.as_a2a(
|
1703
|
+
name="My Assistant API",
|
1704
|
+
description="A helpful AI assistant",
|
1705
|
+
host="localhost",
|
1706
|
+
port=3000
|
1707
|
+
)
|
1708
|
+
```
|
1709
|
+
"""
|
1710
|
+
from ..a2a import as_a2a_app
|
1711
|
+
|
1712
|
+
return as_a2a_app(
|
1713
|
+
self,
|
1714
|
+
context=context,
|
1715
|
+
storage=storage,
|
1716
|
+
broker=broker,
|
1717
|
+
host=host,
|
1718
|
+
port=port,
|
1719
|
+
reload=reload,
|
1720
|
+
workers=workers,
|
1721
|
+
log_level=log_level,
|
1722
|
+
name=name or self.name,
|
1723
|
+
url=url,
|
1724
|
+
version=version,
|
1725
|
+
description=description or self.description,
|
1726
|
+
lifespan_timeout=lifespan_timeout,
|
1727
|
+
**uvicorn_kwargs,
|
1728
|
+
)
|
1729
|
+
|
1207
1730
|
def iter(
|
1208
1731
|
self,
|
1209
1732
|
messages: AgentMessages,
|
@@ -1221,6 +1744,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
1221
1744
|
context_selection_instructions: Optional[str] = None,
|
1222
1745
|
context_update_instructions: Optional[str] = None,
|
1223
1746
|
context_format: Optional[Literal["json", "python", "markdown"]] = None,
|
1747
|
+
end_strategy: Optional[Literal["tool"]] = None,
|
1748
|
+
end_tool: Optional[Callable] = None,
|
1224
1749
|
**kwargs: Any,
|
1225
1750
|
) -> AgentStream[T, AgentContext]:
|
1226
1751
|
"""Iterate over agent steps, yielding each step response.
|
@@ -1314,6 +1839,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
|
|
1314
1839
|
context=context,
|
1315
1840
|
output_type=output_type,
|
1316
1841
|
stream=True,
|
1842
|
+
end_strategy=end_strategy,
|
1843
|
+
end_tool=end_tool,
|
1317
1844
|
**kwargs,
|
1318
1845
|
)
|
1319
1846
|
|
@@ -1370,6 +1897,8 @@ def create_agent(
|
|
1370
1897
|
context_selection_instructions: Optional[str] = None,
|
1371
1898
|
context_update_instructions: Optional[str] = None,
|
1372
1899
|
context_format: Literal["json", "python", "markdown"] = "json",
|
1900
|
+
verbose: bool = False,
|
1901
|
+
debug: bool = False,
|
1373
1902
|
**kwargs: Any,
|
1374
1903
|
) -> Agent[T]:
|
1375
1904
|
"""Create a new AI agent with specified capabilities and behavior.
|
@@ -1394,6 +1923,8 @@ def create_agent(
|
|
1394
1923
|
context_selection_instructions: Custom instructions for context selection
|
1395
1924
|
context_update_instructions: Custom instructions for context updates
|
1396
1925
|
context_format: Format for context display - "json", "python", or "markdown"
|
1926
|
+
verbose: If True, set logger to INFO level for detailed output
|
1927
|
+
debug: If True, set logger to DEBUG level for maximum verbosity
|
1397
1928
|
**kwargs: Additional parameters passed to the underlying language model
|
1398
1929
|
|
1399
1930
|
Example:
|
@@ -1425,6 +1956,8 @@ def create_agent(
|
|
1425
1956
|
context_selection_instructions=context_selection_instructions,
|
1426
1957
|
context_update_instructions=context_update_instructions,
|
1427
1958
|
context_format=context_format,
|
1959
|
+
verbose=verbose,
|
1960
|
+
debug=debug,
|
1428
1961
|
**kwargs,
|
1429
1962
|
)
|
1430
1963
|
|