Skip to content

Commit dff1a71

Browse files
authored
feat: inject VEADK source header into migrated models (#182)
2 parents c7a2fe5 + eba99c7 commit dff1a71

5 files changed

Lines changed: 347 additions & 14 deletions

File tree

‎agentkit/frameworks/model_replacement.py‎

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import os
88
from typing import Any
99

10-
1110
logger = logging.getLogger(__name__)
1211
ARK_DEFAULT_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
12+
VEADK_SOURCE_HEADER = "veadk-source"
13+
VEADK_SOURCE_VALUE = "veadk"
1314
_DISABLED_VALUES = {"0", "false", "off", "none", "disabled"}
15+
_HEADER_PATCH_MARKER = "__agentkit_veadk_source_header__"
1416
_MODEL_PARAM_KEYS = {
1517
"frequency_penalty",
1618
"max_tokens",
@@ -49,6 +51,55 @@ def _replacement_enabled() -> bool:
4951
return value not in _DISABLED_VALUES
5052

5153

54+
def _merge_veadk_source_header(headers: Any = None) -> dict[str, Any]:
55+
merged = {
56+
key: value
57+
for key, value in dict(headers or {}).items()
58+
if key.lower() != VEADK_SOURCE_HEADER
59+
}
60+
merged[VEADK_SOURCE_HEADER] = VEADK_SOURCE_VALUE
61+
return merged
62+
63+
64+
def _header_kwarg_model_cls(base_cls: type[Any], header_kwarg: str) -> type[Any]:
65+
if getattr(base_cls, _HEADER_PATCH_MARKER, False):
66+
return base_cls
67+
68+
class AgentKitHeaderModel(base_cls):
69+
def __init__(self, *args: Any, **kwargs: Any) -> None:
70+
kwargs[header_kwarg] = _merge_veadk_source_header(kwargs.get(header_kwarg))
71+
super().__init__(*args, **kwargs)
72+
73+
AgentKitHeaderModel.__name__ = base_cls.__name__
74+
AgentKitHeaderModel.__qualname__ = base_cls.__qualname__
75+
AgentKitHeaderModel.__module__ = base_cls.__module__
76+
setattr(AgentKitHeaderModel, _HEADER_PATCH_MARKER, True)
77+
return AgentKitHeaderModel
78+
79+
80+
def _openai_client_args_model_cls(base_cls: type[Any]) -> type[Any]:
81+
if getattr(base_cls, _HEADER_PATCH_MARKER, False):
82+
return base_cls
83+
84+
class AgentKitHeaderModel(base_cls):
85+
def __init__(self, *args: Any, **kwargs: Any) -> None:
86+
if args or kwargs.get("client") is not None:
87+
super().__init__(*args, **kwargs)
88+
return
89+
client_args = dict(kwargs.get("client_args") or {})
90+
client_args["default_headers"] = _merge_veadk_source_header(
91+
client_args.get("default_headers")
92+
)
93+
kwargs["client_args"] = client_args
94+
super().__init__(*args, **kwargs)
95+
96+
AgentKitHeaderModel.__name__ = base_cls.__name__
97+
AgentKitHeaderModel.__qualname__ = base_cls.__qualname__
98+
AgentKitHeaderModel.__module__ = base_cls.__module__
99+
setattr(AgentKitHeaderModel, _HEADER_PATCH_MARKER, True)
100+
return AgentKitHeaderModel
101+
102+
52103
def _replacement_model_config(config: dict[str, Any]) -> dict[str, Any]:
53104
model_id = _target_model_id()
54105
if not model_id:
@@ -90,6 +141,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
90141
client_args={
91142
"base_url": _target_base_url(),
92143
"api_key": api_key,
144+
"default_headers": _merge_veadk_source_header(),
93145
},
94146
**_replacement_model_config(kwargs),
95147
)
@@ -101,14 +153,57 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
101153
def _patch_model_attr(module_name: str, attr: str, replacement: type[Any]) -> bool:
102154
try:
103155
module = importlib.import_module(module_name)
104-
except Exception:
156+
except Exception: # noqa: BLE001 - optional framework imports are best effort
105157
return False
106158
if hasattr(module, attr):
107159
setattr(module, attr, replacement)
108160
return True
109161
return False
110162

111163

164+
def _patch_langchain_model_headers() -> bool:
165+
try:
166+
module = importlib.import_module("langchain_openai.chat_models.base")
167+
replacement = _header_kwarg_model_cls(module.ChatOpenAI, "default_headers")
168+
except Exception: # noqa: BLE001 - optional framework imports are best effort
169+
return False
170+
171+
patched = False
172+
for module_name in (
173+
"langchain_openai",
174+
"langchain_openai.chat_models",
175+
"langchain_openai.chat_models.base",
176+
):
177+
patched = _patch_model_attr(module_name, "ChatOpenAI", replacement) or patched
178+
return patched
179+
180+
181+
def _patch_adk_model_headers() -> bool:
182+
try:
183+
module = importlib.import_module("google.adk.models.lite_llm")
184+
replacement = _header_kwarg_model_cls(module.LiteLlm, "extra_headers")
185+
except Exception: # noqa: BLE001 - optional framework imports are best effort
186+
return False
187+
188+
patched = False
189+
for module_name in ("google.adk.models", "google.adk.models.lite_llm"):
190+
patched = _patch_model_attr(module_name, "LiteLlm", replacement) or patched
191+
return patched
192+
193+
194+
def _patch_strands_openai_model_headers() -> bool:
195+
try:
196+
module = importlib.import_module("strands.models.openai")
197+
replacement = _openai_client_args_model_cls(module.OpenAIModel)
198+
except Exception: # noqa: BLE001 - optional framework imports are best effort
199+
return False
200+
201+
patched = False
202+
for module_name in ("strands.models", "strands.models.openai"):
203+
patched = _patch_model_attr(module_name, "OpenAIModel", replacement) or patched
204+
return patched
205+
206+
112207
def _apply_env_aliases() -> bool:
113208
changed = False
114209
model_id = _target_model_id()
@@ -138,7 +233,7 @@ def _apply_env_aliases() -> bool:
138233
def _patch_strands_model_classes() -> bool:
139234
try:
140235
replacement = _agentkit_openai_model_cls()
141-
except Exception:
236+
except Exception: # noqa: BLE001 - optional framework imports are best effort
142237
return False
143238

144239
patched = False
@@ -155,21 +250,33 @@ def apply_agentkit_model_replacement() -> bool:
155250
"""Apply explicit target-model replacement for generated migration wrappers.
156251
157252
The helper intentionally stays low-intrusion: it normalizes common model
158-
environment variables and patches only stable Strands Bedrock/Anthropic
159-
model classes before user code constructs them. It does not rewrite project
160-
source code or chase framework-specific model SDK constructor details.
253+
environment variables and patches stable framework model constructors before
254+
user code constructs them. It does not rewrite project source code or patch
255+
global HTTP/OpenAI clients.
161256
"""
162257

163258
if not _replacement_enabled():
164259
logger.info("AgentKit model replacement disabled by ARK_MODEL_REPLACEMENT.")
165260
return False
166261
env_changed = _apply_env_aliases()
262+
langchain_headers_patched = _patch_langchain_model_headers()
263+
adk_headers_patched = _patch_adk_model_headers()
264+
strands_headers_patched = _patch_strands_openai_model_headers()
167265
patched = _patch_strands_model_classes()
168266
logger.info(
169-
"AgentKit model replacement enabled: env_aliases=%s strands_model_patch=%s target_model_configured=%s base_url_configured=%s",
267+
"AgentKit model replacement enabled: env_aliases=%s langchain_headers=%s adk_headers=%s strands_headers=%s strands_model_patch=%s target_model_configured=%s base_url_configured=%s",
170268
env_changed,
269+
langchain_headers_patched,
270+
adk_headers_patched,
271+
strands_headers_patched,
171272
patched,
172273
bool(_target_model_id()),
173274
bool(_target_base_url()),
174275
)
175-
return env_changed or patched
276+
return (
277+
env_changed
278+
or langchain_headers_patched
279+
or adk_headers_patched
280+
or strands_headers_patched
281+
or patched
282+
)

‎agentkit/version.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
VERSION = "0.8.5"
15+
VERSION = "0.8.6"

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "agentkit-sdk-python"
7-
version = "0.8.5"
7+
version = "0.8.6"
88
description = "Python SDK for transforming any AI agent into a production-ready application. Framework-agnostic primitives for runtime, memory, authentication, and tools with volcengine-managed infrastructure."
99
readme = "README.md"
1010
requires-python = ">=3.10"

0 commit comments

Comments
 (0)