Skip to content

Commit d876072

Browse files
authored
Merge pull request #137 from ai-agent-assembly/v0.0.1/AAASM-3087/refactor/sonar_code_smells
[AAASM-3087] 🧹 (python-sdk): Clear 8 CRITICAL smells + exclude generated proto
2 parents b7c6434 + b31ccf5 commit d876072

6 files changed

Lines changed: 164 additions & 121 deletions

File tree

agent_assembly/__init__.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,34 @@
1010

1111
__version__ = "0.0.2a1"
1212

13+
_MODULE_CORE = "agent_assembly.core"
14+
_MODULE_ADAPTERS = "agent_assembly.adapters"
15+
_MODULE_EXCEPTIONS = "agent_assembly.exceptions"
16+
_MODULE_TYPES = "agent_assembly.types"
17+
_MODULE_NATIVE_CORE = "agent_assembly._core"
18+
1319
# AAASM-1696: top-level exports are resolved lazily so that lightweight
1420
# submodules (e.g. `agent_assembly.runtime`, which is stdlib-only) can be
1521
# imported without dragging in the SDK's third-party dependency surface
1622
# (`httpx`, `pydantic`, …). See PEP 562.
1723
_LAZY_EXPORTS: dict[str, str] = {
18-
"init_assembly": "agent_assembly.core",
19-
"AssemblyContext": "agent_assembly.core",
20-
"GovernanceInterceptor": "agent_assembly.adapters",
21-
"FrameworkAdapter": "agent_assembly.adapters",
22-
"AssemblyError": "agent_assembly.exceptions",
23-
"AgentError": "agent_assembly.exceptions",
24-
"PolicyError": "agent_assembly.exceptions",
25-
"GatewayError": "agent_assembly.exceptions",
26-
"ConfigurationError": "agent_assembly.exceptions",
27-
"AdapterValidationError": "agent_assembly.exceptions",
28-
"ToolExecutionBlockedError": "agent_assembly.exceptions",
29-
"MCPToolBlockedError": "agent_assembly.exceptions",
30-
"AuditEvent": "agent_assembly.types",
31-
"CallStackNode": "agent_assembly.types",
32-
"CallStackNodeKind": "agent_assembly.types",
33-
"GovernanceEvent": "agent_assembly._core",
34-
"RuntimeClient": "agent_assembly._core",
24+
"init_assembly": _MODULE_CORE,
25+
"AssemblyContext": _MODULE_CORE,
26+
"GovernanceInterceptor": _MODULE_ADAPTERS,
27+
"FrameworkAdapter": _MODULE_ADAPTERS,
28+
"AssemblyError": _MODULE_EXCEPTIONS,
29+
"AgentError": _MODULE_EXCEPTIONS,
30+
"PolicyError": _MODULE_EXCEPTIONS,
31+
"GatewayError": _MODULE_EXCEPTIONS,
32+
"ConfigurationError": _MODULE_EXCEPTIONS,
33+
"AdapterValidationError": _MODULE_EXCEPTIONS,
34+
"ToolExecutionBlockedError": _MODULE_EXCEPTIONS,
35+
"MCPToolBlockedError": _MODULE_EXCEPTIONS,
36+
"AuditEvent": _MODULE_TYPES,
37+
"CallStackNode": _MODULE_TYPES,
38+
"CallStackNodeKind": _MODULE_TYPES,
39+
"GovernanceEvent": _MODULE_NATIVE_CORE,
40+
"RuntimeClient": _MODULE_NATIVE_CORE,
3541
}
3642

3743
_ALWAYS_EXPORTED: list[str] = [
@@ -60,10 +66,10 @@
6066

6167

6268
def _core_available() -> bool:
63-
if "agent_assembly._core" in sys.modules:
69+
if _MODULE_NATIVE_CORE in sys.modules:
6470
return True
6571
try:
66-
return importlib.util.find_spec("agent_assembly._core") is not None
72+
return importlib.util.find_spec(_MODULE_NATIVE_CORE) is not None
6773
except (ModuleNotFoundError, ValueError):
6874
return False
6975

@@ -80,7 +86,7 @@ def __getattr__(name: str) -> Any:
8086
try:
8187
module = importlib.import_module(module_name)
8288
except ImportError:
83-
if module_name == "agent_assembly._core":
89+
if module_name == _MODULE_NATIVE_CORE:
8490
raise AttributeError(
8591
f"module 'agent_assembly' has no attribute {name!r}: the native '_core' extension is not built"
8692
) from None

agent_assembly/adapters/crewai/patch.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -135,29 +135,29 @@ def _get_thread_local_agent_id() -> str | None:
135135
return None
136136

137137

138+
def _nonempty_str_agent_id(source: Any) -> str | None:
139+
"""Return ``source["agent_id"]`` when ``source`` is a dict holding a non-empty str."""
140+
if not isinstance(source, dict):
141+
return None
142+
value = source.get("agent_id")
143+
if isinstance(value, str) and value:
144+
return value
145+
return None
146+
147+
138148
def _extract_agent_id_from_inputs(args: tuple[Any, ...], kwargs: dict[str, Any]) -> str | None:
139-
direct_agent_id = kwargs.get("agent_id")
140-
if isinstance(direct_agent_id, str) and direct_agent_id:
149+
direct_agent_id = _nonempty_str_agent_id(kwargs)
150+
if direct_agent_id is not None:
141151
return direct_agent_id
142152

143153
config = kwargs.get("config")
144154
if isinstance(config, dict):
145-
configurable = config.get("configurable")
146-
if isinstance(configurable, dict):
147-
configurable_agent_id = configurable.get("agent_id")
148-
if isinstance(configurable_agent_id, str) and configurable_agent_id:
149-
return configurable_agent_id
150-
151-
metadata = config.get("metadata")
152-
if isinstance(metadata, dict):
153-
metadata_agent_id = metadata.get("agent_id")
154-
if isinstance(metadata_agent_id, str) and metadata_agent_id:
155-
return metadata_agent_id
156-
157-
if args and isinstance(args[0], dict):
158-
state_agent_id = args[0].get("agent_id")
159-
if isinstance(state_agent_id, str) and state_agent_id:
160-
return state_agent_id
155+
nested = _nonempty_str_agent_id(config.get("configurable")) or _nonempty_str_agent_id(config.get("metadata"))
156+
if nested is not None:
157+
return nested
158+
159+
if args:
160+
return _nonempty_str_agent_id(args[0])
161161

162162
return None
163163

agent_assembly/adapters/langgraph/patch.py

Lines changed: 78 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
_ORIGINAL_COMPILE = "_agent_assembly_original_compile"
1616
_NODE_WRAPPED_FLAG = "_agent_assembly_node_wrapped"
1717
_INVOKE_WRAPPED_FLAG = "_agent_assembly_invoke_wrapped"
18+
_GRAPH_INVOKE_NODE_NAME = "graph.invoke"
1819

1920
# Thread-local storage for the name of the most-recently-completed node so that
2021
# the next _record_node_enter can emit a directed Messages edge between them.
@@ -308,69 +309,86 @@ def sync_subgraph_wrapper(*args: Any, **kwargs: Any) -> Any:
308309
return sync_subgraph_wrapper
309310

310311

312+
def _wrap_callable_node_executor(node_map: Any, node_name: Any, node_executor: Any, callback_handler: Any) -> bool:
313+
"""Wrap a directly-callable node executor in the node map. Return True if replaced."""
314+
wrapped_executor = _make_assembly_node_wrapper(str(node_name), node_executor, callback_handler)
315+
if wrapped_executor is node_executor:
316+
return False
317+
try:
318+
node_map[node_name] = wrapped_executor
319+
except Exception:
320+
return False
321+
return True
322+
323+
324+
def _wrap_subgraph_spawn_node(node_map: Any, node_name: Any, node_executor: Any, process_agent_id: str | None) -> bool:
325+
"""Wrap a compiled-subgraph node (spawn point) for lineage. Return True when wrapped."""
326+
node_delegation_reason = f"langgraph_node:{node_name}"
327+
sync_wrapper = _make_subgraph_spawn_wrapper(
328+
str(node_name),
329+
node_executor,
330+
process_agent_id,
331+
spawned_by_tool=None,
332+
delegation_reason=node_delegation_reason,
333+
)
334+
with contextlib.suppress(Exception):
335+
node_map[node_name] = sync_wrapper
336+
if hasattr(node_executor, "ainvoke") and not getattr(node_executor, "_agent_assembly_ainvoke_spawned", False):
337+
async_wrapper = _make_subgraph_spawn_wrapper(
338+
str(node_name),
339+
node_executor,
340+
process_agent_id,
341+
async_=True,
342+
spawned_by_tool=None,
343+
delegation_reason=node_delegation_reason,
344+
)
345+
node_executor.ainvoke = async_wrapper
346+
node_executor._agent_assembly_ainvoke_spawned = True
347+
return True
348+
349+
350+
def _wrap_node_invoke_methods(node_name: Any, node_executor: Any, callback_handler: Any) -> bool:
351+
"""Wrap a node executor's invoke/ainvoke methods. Return True if either was wrapped."""
352+
wrapped_any = False
353+
invoke = getattr(node_executor, "invoke", None)
354+
if callable(invoke):
355+
node_executor.invoke = _make_assembly_node_wrapper(str(node_name), invoke, callback_handler)
356+
wrapped_any = True
357+
358+
ainvoke = getattr(node_executor, "ainvoke", None)
359+
if callable(ainvoke):
360+
node_executor.ainvoke = _make_assembly_node_wrapper(str(node_name), ainvoke, callback_handler)
361+
wrapped_any = True
362+
return wrapped_any
363+
364+
365+
def _wrap_node_entry(
366+
node_map: Any, node_name: Any, node_executor: Any, callback_handler: Any, process_agent_id: str | None
367+
) -> bool:
368+
"""Dispatch a single (node_name, node_executor) entry to the right wrapper. Return True if wrapped."""
369+
# ToolNode: intercept any compiled-subgraph tools it holds.
370+
# Must come before the callable() check since ToolNode is also callable.
371+
if _is_tool_node(node_executor):
372+
return _wrap_tool_node_subgraphs(str(node_name), node_executor, process_agent_id)
373+
374+
if callable(node_executor):
375+
return _wrap_callable_node_executor(node_map, node_name, node_executor, callback_handler)
376+
377+
# Spawn point: node is itself a compiled subgraph — wrap for lineage.
378+
if _is_compiled_subgraph(node_executor):
379+
return _wrap_subgraph_spawn_node(node_map, node_name, node_executor, process_agent_id)
380+
381+
return _wrap_node_invoke_methods(node_name, node_executor, callback_handler)
382+
383+
311384
def _wrap_node_map(node_map: Any, callback_handler: Any, process_agent_id: str | None = None) -> bool:
312385
items_method = getattr(node_map, "items", None)
313386
if not callable(items_method):
314387
return False
315388

316389
wrapped_any = False
317390
for node_name, node_executor in list(items_method()):
318-
# ToolNode: intercept any compiled-subgraph tools it holds.
319-
# Must come before the callable() check since ToolNode is also callable.
320-
if _is_tool_node(node_executor):
321-
if _wrap_tool_node_subgraphs(str(node_name), node_executor, process_agent_id):
322-
wrapped_any = True
323-
continue
324-
325-
if callable(node_executor):
326-
wrapped_executor = _make_assembly_node_wrapper(str(node_name), node_executor, callback_handler)
327-
if wrapped_executor is node_executor:
328-
continue
329-
try:
330-
node_map[node_name] = wrapped_executor
331-
except Exception:
332-
continue
333-
wrapped_any = True
334-
continue
335-
336-
# Spawn point: node is itself a compiled subgraph — wrap for lineage.
337-
if _is_compiled_subgraph(node_executor):
338-
node_delegation_reason = f"langgraph_node:{node_name}"
339-
sync_wrapper = _make_subgraph_spawn_wrapper(
340-
str(node_name),
341-
node_executor,
342-
process_agent_id,
343-
spawned_by_tool=None,
344-
delegation_reason=node_delegation_reason,
345-
)
346-
with contextlib.suppress(Exception):
347-
node_map[node_name] = sync_wrapper
348-
if hasattr(node_executor, "ainvoke") and not getattr(
349-
node_executor, "_agent_assembly_ainvoke_spawned", False
350-
):
351-
async_wrapper = _make_subgraph_spawn_wrapper(
352-
str(node_name),
353-
node_executor,
354-
process_agent_id,
355-
async_=True,
356-
spawned_by_tool=None,
357-
delegation_reason=node_delegation_reason,
358-
)
359-
node_executor.ainvoke = async_wrapper
360-
node_executor._agent_assembly_ainvoke_spawned = True
361-
wrapped_any = True
362-
continue
363-
364-
invoke = getattr(node_executor, "invoke", None)
365-
if callable(invoke):
366-
wrapped_invoke = _make_assembly_node_wrapper(str(node_name), invoke, callback_handler)
367-
node_executor.invoke = wrapped_invoke
368-
wrapped_any = True
369-
370-
ainvoke = getattr(node_executor, "ainvoke", None)
371-
if callable(ainvoke):
372-
wrapped_ainvoke = _make_assembly_node_wrapper(str(node_name), ainvoke, callback_handler)
373-
node_executor.ainvoke = wrapped_ainvoke
391+
if _wrap_node_entry(node_map, node_name, node_executor, callback_handler, process_agent_id):
374392
wrapped_any = True
375393

376394
return wrapped_any
@@ -432,11 +450,11 @@ def _wrap_graph_invoke_fallback(compiled_graph: Any, callback_handler: Any) -> N
432450
async def wrapped_async_invoke(*invoke_args: Any, **invoke_kwargs: Any) -> Any:
433451
state = _extract_state(invoke_args, invoke_kwargs)
434452
config = _extract_config(invoke_args, invoke_kwargs)
435-
_record_node_enter(callback_handler, node_name="graph.invoke", state=state, config=config)
453+
_record_node_enter(callback_handler, node_name=_GRAPH_INVOKE_NODE_NAME, state=state, config=config)
436454
result = await invoke(*invoke_args, **invoke_kwargs)
437455
_record_node_exit(
438456
callback_handler,
439-
node_name="graph.invoke",
457+
node_name=_GRAPH_INVOKE_NODE_NAME,
440458
previous_state=state,
441459
next_state=result,
442460
config=config,
@@ -449,11 +467,11 @@ async def wrapped_async_invoke(*invoke_args: Any, **invoke_kwargs: Any) -> Any:
449467
def wrapped_sync_invoke(*invoke_args: Any, **invoke_kwargs: Any) -> Any:
450468
state = _extract_state(invoke_args, invoke_kwargs)
451469
config = _extract_config(invoke_args, invoke_kwargs)
452-
_record_node_enter(callback_handler, node_name="graph.invoke", state=state, config=config)
470+
_record_node_enter(callback_handler, node_name=_GRAPH_INVOKE_NODE_NAME, state=state, config=config)
453471
result = invoke(*invoke_args, **invoke_kwargs)
454472
_record_node_exit(
455473
callback_handler,
456-
node_name="graph.invoke",
474+
node_name=_GRAPH_INVOKE_NODE_NAME,
457475
previous_state=state,
458476
next_state=result,
459477
config=config,

agent_assembly/adapters/openai_agents/patch.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
_EDGE_EMITTER: Any = None
2828
_MAX_AUDIT_RESULT_CHARS = 2000
2929
_MAX_DELEGATION_REASON_CHARS = 256
30+
_OPENAI_AGENTS_MODULE = "openai.agents"
3031

3132

3233
def set_edge_emitter(emitter: Any) -> None:
@@ -74,7 +75,7 @@ def revert(self) -> None:
7475

7576

7677
def _is_openai_agents_available() -> bool:
77-
return importlib.util.find_spec("openai.agents") is not None
78+
return importlib.util.find_spec(_OPENAI_AGENTS_MODULE) is not None
7879

7980

8081
def set_process_agent_id(agent_id: str | None) -> None:
@@ -90,7 +91,7 @@ def _get_process_agent_id() -> str | None:
9091

9192
def _load_openai_agents_function_tool_class() -> type[Any] | None:
9293
try:
93-
module = importlib.import_module("openai.agents")
94+
module = importlib.import_module(_OPENAI_AGENTS_MODULE)
9495
except ImportError:
9596
return None
9697

@@ -102,7 +103,7 @@ def _load_openai_agents_function_tool_class() -> type[Any] | None:
102103

103104
def _load_openai_agents_runner_class() -> type[Any] | None:
104105
try:
105-
module = importlib.import_module("openai.agents")
106+
module = importlib.import_module(_OPENAI_AGENTS_MODULE)
106107
except ImportError:
107108
return None
108109
runner_cls = getattr(module, "Runner", None)
@@ -113,7 +114,7 @@ def _load_openai_agents_runner_class() -> type[Any] | None:
113114

114115
def _load_openai_agents_handoff_class() -> type[Any] | None:
115116
try:
116-
module = importlib.import_module("openai.agents")
117+
module = importlib.import_module(_OPENAI_AGENTS_MODULE)
117118
except ImportError:
118119
return None
119120
handoff_cls = getattr(module, "Handoff", None)
@@ -330,7 +331,7 @@ def _build_tool_result_error(
330331
is_pending_rejection: bool,
331332
) -> object:
332333
try:
333-
module = importlib.import_module("openai.agents")
334+
module = importlib.import_module(_OPENAI_AGENTS_MODULE)
334335
except ImportError:
335336
module = None
336337

0 commit comments

Comments
 (0)