hammad-python 0.0.24__py3-none-any.whl → 0.0.26__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.
@@ -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
- pass
53
+ try:
54
+ from fasta2a import FastA2A
55
+ except ImportError:
56
+ FastA2A: TypeAlias = Any
53
57
 
54
58
 
55
59
  T = TypeVar("T")
@@ -1058,7 +1062,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
1058
1062
  f"Agent '{self.name}' completed execution in {step + 1} steps"
1059
1063
  )
1060
1064
  # Now we can make the final call with the output_type if specified
1061
- if output_type:
1065
+ # Only make structured output call for non-str types
1066
+ if output_type and output_type != str:
1062
1067
  # Make a final call with the structured output type
1063
1068
  final_model_kwargs = kwargs.copy()
1064
1069
  final_model_kwargs["type"] = output_type
@@ -1129,8 +1134,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
1129
1134
  # Max steps reached - return last response
1130
1135
  if steps:
1131
1136
  final_response = steps[-1]
1132
- # If we have an output_type, make a final structured call
1133
- if output_type:
1137
+ # If we have an output_type, make a final structured call (but not for str)
1138
+ if output_type and output_type != str:
1134
1139
  final_model_kwargs = kwargs.copy()
1135
1140
  final_model_kwargs["type"] = output_type
1136
1141
  if self.instructor_mode:
@@ -1168,7 +1173,7 @@ Please update the appropriate fields based on the conversation. Only update fiel
1168
1173
  else:
1169
1174
  # No steps taken, make a final call
1170
1175
  final_model_kwargs = kwargs.copy()
1171
- if output_type:
1176
+ if output_type and output_type != str:
1172
1177
  final_model_kwargs["type"] = output_type
1173
1178
  if self.instructor_mode:
1174
1179
  final_model_kwargs["instructor_mode"] = self.instructor_mode
@@ -1444,7 +1449,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
1444
1449
 
1445
1450
  # This is the final step (either no end_strategy or end_tool was called)
1446
1451
  # Now we can make the final call with the output_type if specified
1447
- if output_type:
1452
+ # Only make structured output call for non-str types
1453
+ if output_type and output_type != str:
1448
1454
  # Make a final call with the structured output type
1449
1455
  final_model_kwargs = kwargs.copy()
1450
1456
  final_model_kwargs["type"] = output_type
@@ -1515,8 +1521,8 @@ Please update the appropriate fields based on the conversation. Only update fiel
1515
1521
  # Max steps reached - return last response
1516
1522
  if steps:
1517
1523
  final_response = steps[-1]
1518
- # If we have an output_type, make a final structured call
1519
- if output_type:
1524
+ # If we have an output_type, make a final structured call (but not for str)
1525
+ if output_type and output_type != str:
1520
1526
  final_model_kwargs = kwargs.copy()
1521
1527
  final_model_kwargs["type"] = output_type
1522
1528
  if self.instructor_mode:
@@ -1554,7 +1560,7 @@ Please update the appropriate fields based on the conversation. Only update fiel
1554
1560
  else:
1555
1561
  # No steps taken, make a final call
1556
1562
  final_model_kwargs = kwargs.copy()
1557
- if output_type:
1563
+ if output_type and output_type != str:
1558
1564
  final_model_kwargs["type"] = output_type
1559
1565
  if self.instructor_mode:
1560
1566
  final_model_kwargs["instructor_mode"] = self.instructor_mode
@@ -1621,6 +1627,106 @@ Please update the appropriate fields based on the conversation. Only update fiel
1621
1627
  **kwargs,
1622
1628
  )
1623
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
+
1624
1730
  def iter(
1625
1731
  self,
1626
1732
  messages: AgentMessages,
@@ -3,6 +3,7 @@
3
3
  Standalone functions for running agents with full parameter typing.
4
4
  """
5
5
 
6
+ import functools
6
7
  from typing import (
7
8
  Any,
8
9
  Callable,
@@ -40,6 +41,7 @@ __all__ = [
40
41
  "async_run_agent",
41
42
  "run_agent_iter",
42
43
  "async_run_agent_iter",
44
+ "agent_decorator",
43
45
  ]
44
46
 
45
47
  T = TypeVar("T")
@@ -209,8 +211,20 @@ def run_agent(
209
211
  ... context=context
210
212
  ... )
211
213
  """
212
- agent = Agent(verbose=verbose, debug=debug, **kwargs)
213
- return agent.run(messages, verbose=verbose, debug=debug, **kwargs)
214
+ # Separate agent constructor parameters from run parameters
215
+ agent_constructor_params = {
216
+ k: v
217
+ for k, v in kwargs.items()
218
+ if k in ["name", "instructions", "description", "tools", "settings", "model"]
219
+ }
220
+ agent_run_params = {
221
+ k: v
222
+ for k, v in kwargs.items()
223
+ if k not in ["name", "instructions", "description", "tools", "settings"]
224
+ }
225
+
226
+ agent = Agent(verbose=verbose, debug=debug, **agent_constructor_params)
227
+ return agent.run(messages, verbose=verbose, debug=debug, **agent_run_params)
214
228
 
215
229
 
216
230
  # Overloads for async_run_agent
@@ -371,8 +385,22 @@ async def async_run_agent(
371
385
  ... )
372
386
  ... return response.output
373
387
  """
374
- agent = Agent(verbose=verbose, debug=debug, **kwargs)
375
- return await agent.async_run(messages, verbose=verbose, debug=debug, **kwargs)
388
+ # Separate agent constructor parameters from run parameters
389
+ agent_constructor_params = {
390
+ k: v
391
+ for k, v in kwargs.items()
392
+ if k in ["name", "instructions", "description", "tools", "settings", "model"]
393
+ }
394
+ agent_run_params = {
395
+ k: v
396
+ for k, v in kwargs.items()
397
+ if k not in ["name", "instructions", "description", "tools", "settings"]
398
+ }
399
+
400
+ agent = Agent(verbose=verbose, debug=debug, **agent_constructor_params)
401
+ return await agent.async_run(
402
+ messages, verbose=verbose, debug=debug, **agent_run_params
403
+ )
376
404
 
377
405
 
378
406
  # Overloads for run_agent_iter
@@ -545,8 +573,22 @@ def run_agent_iter(
545
573
  ... except Exception as e:
546
574
  ... print(f"Stream error: {e}")
547
575
  """
548
- agent = Agent(verbose=verbose, debug=debug, **kwargs)
549
- return agent.run(messages, stream=True, verbose=verbose, debug=debug, **kwargs)
576
+ # Separate agent constructor parameters from run parameters
577
+ agent_constructor_params = {
578
+ k: v
579
+ for k, v in kwargs.items()
580
+ if k in ["name", "instructions", "description", "tools", "settings", "model"]
581
+ }
582
+ agent_run_params = {
583
+ k: v
584
+ for k, v in kwargs.items()
585
+ if k not in ["name", "instructions", "description", "tools", "settings"]
586
+ }
587
+
588
+ agent = Agent(verbose=verbose, debug=debug, **agent_constructor_params)
589
+ return agent.run(
590
+ messages, stream=True, verbose=verbose, debug=debug, **agent_run_params
591
+ )
550
592
 
551
593
 
552
594
  # Overloads for async_run_agent_iter
@@ -649,5 +691,334 @@ def async_run_agent_iter(
649
691
  Returns:
650
692
  An AgentStream that can be iterated over asynchronously
651
693
  """
652
- agent = Agent(verbose=verbose, debug=debug, **kwargs)
653
- return agent.run(messages, stream=True, verbose=verbose, debug=debug, **kwargs)
694
+ # Separate agent constructor parameters from run parameters
695
+ agent_constructor_params = {
696
+ k: v
697
+ for k, v in kwargs.items()
698
+ if k in ["name", "instructions", "description", "tools", "settings", "model"]
699
+ }
700
+ agent_run_params = {
701
+ k: v
702
+ for k, v in kwargs.items()
703
+ if k not in ["name", "instructions", "description", "tools", "settings"]
704
+ }
705
+
706
+ agent = Agent(verbose=verbose, debug=debug, **agent_constructor_params)
707
+ return agent.run(
708
+ messages, stream=True, verbose=verbose, debug=debug, **agent_run_params
709
+ )
710
+
711
+
712
+ def agent_decorator(
713
+ fn: Union[str, Callable, None] = None,
714
+ *,
715
+ # Agent settings
716
+ name: Optional[str] = None,
717
+ instructions: Optional[str] = None,
718
+ description: Optional[str] = None,
719
+ tools: Union[List["Tool"], Callable, None] = None,
720
+ settings: Optional[AgentSettings] = None,
721
+ # Context management
722
+ context: Optional["AgentContext"] = None,
723
+ context_updates: Optional[
724
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
725
+ ] = None,
726
+ context_confirm: bool = False,
727
+ context_strategy: Literal["selective", "all"] = "all",
728
+ context_max_retries: int = 3,
729
+ context_confirm_instructions: Optional[str] = None,
730
+ context_selection_instructions: Optional[str] = None,
731
+ context_update_instructions: Optional[str] = None,
732
+ context_format: Literal["json", "python", "markdown"] = "json",
733
+ # Model settings
734
+ model: Optional[Union["LanguageModel", "LanguageModelName"]] = None,
735
+ max_steps: Optional[int] = None,
736
+ instructor_mode: Optional["LanguageModelInstructorMode"] = None,
737
+ return_output: bool = True,
738
+ # End strategy
739
+ end_strategy: Optional[Literal["tool"]] = None,
740
+ end_tool: Optional[Callable] = None,
741
+ # LM settings
742
+ timeout: Optional[Union[float, str, "Timeout"]] = None,
743
+ temperature: Optional[float] = None,
744
+ top_p: Optional[float] = None,
745
+ max_tokens: Optional[int] = None,
746
+ presence_penalty: Optional[float] = None,
747
+ frequency_penalty: Optional[float] = None,
748
+ seed: Optional[int] = None,
749
+ user: Optional[str] = None,
750
+ verbose: bool = False,
751
+ debug: bool = False,
752
+ ):
753
+ """Decorator that converts a function into an agent.
754
+
755
+ The function's parameters become the input to the LLM (converted to a string),
756
+ the function's return type annotation becomes the agent's output type,
757
+ and the function's docstring becomes the agent's instructions.
758
+
759
+ Works with both sync and async functions.
760
+
761
+ Can be used in multiple ways:
762
+
763
+ 1. As a decorator with parameters:
764
+ @agent_decorator(name="steve", temperature=0.7)
765
+ def my_agent():
766
+ pass
767
+
768
+ 2. As a decorator without parameters:
769
+ @agent_decorator
770
+ def my_agent():
771
+ pass
772
+
773
+ 3. As an inline function with name as first argument:
774
+ agent = agent_decorator("steve")
775
+ # Then use: decorated_func = agent(my_function)
776
+
777
+ 4. As an inline function with all parameters:
778
+ agent = agent_decorator(name="steve", temperature=0.7)
779
+ # Then use: decorated_func = agent(my_function)
780
+ """
781
+ # Handle different calling patterns
782
+ if callable(fn):
783
+ # Case: @agent_decorator (no parentheses)
784
+ func = fn
785
+ actual_name = name or "agent"
786
+ return _create_agent_wrapper(
787
+ func,
788
+ actual_name,
789
+ instructions,
790
+ description,
791
+ tools,
792
+ settings,
793
+ context,
794
+ context_updates,
795
+ context_confirm,
796
+ context_strategy,
797
+ context_max_retries,
798
+ context_confirm_instructions,
799
+ context_selection_instructions,
800
+ context_update_instructions,
801
+ context_format,
802
+ model,
803
+ max_steps,
804
+ instructor_mode,
805
+ return_output,
806
+ end_strategy,
807
+ end_tool,
808
+ timeout,
809
+ temperature,
810
+ top_p,
811
+ max_tokens,
812
+ presence_penalty,
813
+ frequency_penalty,
814
+ seed,
815
+ user,
816
+ verbose,
817
+ debug,
818
+ )
819
+ elif isinstance(fn, str):
820
+ # Case: agent_decorator("steve") - first arg is name
821
+ actual_name = fn
822
+ else:
823
+ # Case: agent_decorator() or agent_decorator(name="steve")
824
+ actual_name = name or "agent"
825
+
826
+ def decorator(func: Callable) -> Callable:
827
+ return _create_agent_wrapper(
828
+ func,
829
+ actual_name,
830
+ instructions,
831
+ description,
832
+ tools,
833
+ settings,
834
+ context,
835
+ context_updates,
836
+ context_confirm,
837
+ context_strategy,
838
+ context_max_retries,
839
+ context_confirm_instructions,
840
+ context_selection_instructions,
841
+ context_update_instructions,
842
+ context_format,
843
+ model,
844
+ max_steps,
845
+ instructor_mode,
846
+ return_output,
847
+ end_strategy,
848
+ end_tool,
849
+ timeout,
850
+ temperature,
851
+ top_p,
852
+ max_tokens,
853
+ presence_penalty,
854
+ frequency_penalty,
855
+ seed,
856
+ user,
857
+ verbose,
858
+ debug,
859
+ )
860
+
861
+ return decorator
862
+
863
+
864
+ def _create_agent_wrapper(
865
+ func: Callable,
866
+ name: str,
867
+ instructions: Optional[str],
868
+ description: Optional[str],
869
+ tools: Union[List["Tool"], Callable, None],
870
+ settings: Optional[AgentSettings],
871
+ context: Optional["AgentContext"],
872
+ context_updates: Optional[
873
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
874
+ ],
875
+ context_confirm: bool,
876
+ context_strategy: Literal["selective", "all"],
877
+ context_max_retries: int,
878
+ context_confirm_instructions: Optional[str],
879
+ context_selection_instructions: Optional[str],
880
+ context_update_instructions: Optional[str],
881
+ context_format: Literal["json", "python", "markdown"],
882
+ model: Optional[Union["LanguageModel", "LanguageModelName"]],
883
+ max_steps: Optional[int],
884
+ instructor_mode: Optional["LanguageModelInstructorMode"],
885
+ return_output: bool,
886
+ end_strategy: Optional[Literal["tool"]],
887
+ end_tool: Optional[Callable],
888
+ timeout: Optional[Union[float, str, "Timeout"]],
889
+ temperature: Optional[float],
890
+ top_p: Optional[float],
891
+ max_tokens: Optional[int],
892
+ presence_penalty: Optional[float],
893
+ frequency_penalty: Optional[float],
894
+ seed: Optional[int],
895
+ user: Optional[str],
896
+ verbose: bool,
897
+ debug: bool,
898
+ ) -> Callable:
899
+ """Helper function to create the actual agent wrapper."""
900
+ import inspect
901
+ import asyncio
902
+ from typing import get_type_hints
903
+
904
+ # Get function metadata
905
+ sig = inspect.signature(func)
906
+ type_hints = get_type_hints(func)
907
+ return_type = type_hints.get("return", str)
908
+ func_instructions = instructions or func.__doc__ or ""
909
+
910
+ # Check if function is async
911
+ is_async = asyncio.iscoroutinefunction(func)
912
+
913
+ if is_async:
914
+
915
+ @functools.wraps(func)
916
+ async def async_wrapper(*args, **kwargs):
917
+ # Convert function parameters to message string
918
+ bound_args = sig.bind(*args, **kwargs)
919
+ bound_args.apply_defaults()
920
+
921
+ # Create message from parameters
922
+ param_parts = []
923
+ for param_name, param_value in bound_args.arguments.items():
924
+ param_parts.append(f"{param_name}: {param_value}")
925
+ message = "\n".join(param_parts)
926
+
927
+ # Run agent with extracted parameters
928
+ response = await async_run_agent(
929
+ messages=message,
930
+ output_type=return_type,
931
+ name=name,
932
+ instructions=func_instructions,
933
+ description=description,
934
+ tools=tools,
935
+ settings=settings,
936
+ context=context,
937
+ context_updates=context_updates,
938
+ context_confirm=context_confirm,
939
+ context_strategy=context_strategy,
940
+ context_max_retries=context_max_retries,
941
+ context_confirm_instructions=context_confirm_instructions,
942
+ context_selection_instructions=context_selection_instructions,
943
+ context_update_instructions=context_update_instructions,
944
+ context_format=context_format,
945
+ model=model or "openai/gpt-4o-mini",
946
+ max_steps=max_steps,
947
+ instructor_mode=instructor_mode,
948
+ end_strategy=end_strategy,
949
+ end_tool=end_tool,
950
+ timeout=timeout,
951
+ temperature=temperature,
952
+ top_p=top_p,
953
+ max_tokens=max_tokens,
954
+ presence_penalty=presence_penalty,
955
+ frequency_penalty=frequency_penalty,
956
+ seed=seed,
957
+ user=user,
958
+ verbose=verbose,
959
+ debug=debug,
960
+ )
961
+
962
+ # Return just the output if return_output is True (default behavior)
963
+ if return_output:
964
+ return response.output
965
+ else:
966
+ return response
967
+
968
+ return async_wrapper
969
+ else:
970
+
971
+ @functools.wraps(func)
972
+ def sync_wrapper(*args, **kwargs):
973
+ # Convert function parameters to message string
974
+ bound_args = sig.bind(*args, **kwargs)
975
+ bound_args.apply_defaults()
976
+
977
+ # Create message from parameters
978
+ param_parts = []
979
+ for param_name, param_value in bound_args.arguments.items():
980
+ param_parts.append(f"{param_name}: {param_value}")
981
+ message = "\n".join(param_parts)
982
+
983
+ # Run agent with extracted parameters
984
+ response = run_agent(
985
+ messages=message,
986
+ output_type=return_type,
987
+ name=name,
988
+ instructions=func_instructions,
989
+ description=description,
990
+ tools=tools,
991
+ settings=settings,
992
+ context=context,
993
+ context_updates=context_updates,
994
+ context_confirm=context_confirm,
995
+ context_strategy=context_strategy,
996
+ context_max_retries=context_max_retries,
997
+ context_confirm_instructions=context_confirm_instructions,
998
+ context_selection_instructions=context_selection_instructions,
999
+ context_update_instructions=context_update_instructions,
1000
+ context_format=context_format,
1001
+ model=model or "openai/gpt-4o-mini",
1002
+ max_steps=max_steps,
1003
+ instructor_mode=instructor_mode,
1004
+ end_strategy=end_strategy,
1005
+ end_tool=end_tool,
1006
+ timeout=timeout,
1007
+ temperature=temperature,
1008
+ top_p=top_p,
1009
+ max_tokens=max_tokens,
1010
+ presence_penalty=presence_penalty,
1011
+ frequency_penalty=frequency_penalty,
1012
+ seed=seed,
1013
+ user=user,
1014
+ verbose=verbose,
1015
+ debug=debug,
1016
+ )
1017
+
1018
+ # Return just the output if return_output is True (default behavior)
1019
+ if return_output:
1020
+ return response.output
1021
+ else:
1022
+ return response
1023
+
1024
+ return sync_wrapper
@@ -46,7 +46,16 @@ from ..._internal import create_getattr_importer
46
46
 
47
47
 
48
48
  if TYPE_CHECKING:
49
- from .base import BaseGraph, action, ActionNode, GraphBuilder
49
+ from .base import (
50
+ ActionDecorator,
51
+ ActionNode,
52
+ ActionSettings,
53
+ BaseGraph,
54
+ GraphBuilder,
55
+ action,
56
+ select,
57
+ SelectionStrategy,
58
+ )
50
59
  from .types import (
51
60
  GraphContext,
52
61
  GraphResponse,
@@ -77,11 +86,14 @@ __all__ = (
77
86
  # Core graph classes
78
87
  "BaseGraph",
79
88
  "GraphBuilder",
89
+ "ActionDecorator",
80
90
  # Action system
81
91
  "action",
82
92
  "ActionNode",
83
93
  "ActionSettings",
84
94
  "ActionInfo",
95
+ "select",
96
+ "SelectionStrategy",
85
97
  # Plugin system
86
98
  "plugin",
87
99
  "BasePlugin",