ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
@@ -0,0 +1,37 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
import abc
|
||||
|
||||
from aworld.core.context.base import Context
|
||||
from aworld.core.event.base import Message
|
||||
from aworld.runners.hook.hook_factory import HookFactory
|
||||
from aworld.runners.hook.hooks import PostLLMCallHook, PreLLMCallHook
|
||||
from aworld.utils.common import convert_to_snake
|
||||
|
||||
|
||||
@HookFactory.register(name="PreLLMCallContextProcessHook",
|
||||
desc="PreLLMCallContextProcessHook")
|
||||
class PreLLMCallContextProcessHook(PreLLMCallHook):
|
||||
"""Process in the hook point of the pre_llm_call."""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def name(self):
|
||||
return convert_to_snake("PreLLMCallContextProcessHook")
|
||||
|
||||
async def exec(self, message: Message, context: Context = None) -> Message:
|
||||
# and do something
|
||||
pass
|
||||
|
||||
@HookFactory.register(name="PostLLMCallContextProcessHook",
|
||||
desc="PostLLMCallContextProcessHook")
|
||||
class PostLLMCallContextProcessHook(PostLLMCallHook):
|
||||
"""Process in the hook point of the post_llm_call."""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def name(self):
|
||||
return convert_to_snake("PostLLMCallContextProcessHook")
|
||||
|
||||
async def exec(self, message: Message, context: Context = None) -> Message:
|
||||
# get context
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
import sys
|
||||
from typing import Dict, List
|
||||
|
||||
from aworld.core.factory import Factory
|
||||
from aworld.logs.util import logger
|
||||
from aworld.runners.hook.hooks import Hook, StartHook, HookPoint
|
||||
|
||||
|
||||
class HookManager(Factory):
|
||||
def __init__(self, type_name: str = None):
|
||||
super(HookManager, self).__init__(type_name)
|
||||
|
||||
def __call__(self, name: str, **kwargs):
|
||||
if name is None:
|
||||
raise ValueError("hook name is None")
|
||||
|
||||
try:
|
||||
if name in self._cls:
|
||||
act = self._cls[name](**kwargs)
|
||||
else:
|
||||
raise RuntimeError("The hook was not registered.\nPlease confirm the package has been imported.")
|
||||
except Exception:
|
||||
err = sys.exc_info()
|
||||
logger.warning(f"Failed to create hook with name {name}:\n{err[1]}")
|
||||
act = None
|
||||
return act
|
||||
|
||||
def hooks(self, name: str = None) -> Dict[str, List[Hook]]:
|
||||
vals = list(filter(lambda s: not s.startswith('__'), dir(HookPoint)))
|
||||
results = {val.lower(): [] for val in vals}
|
||||
|
||||
for k, v in self._cls.items():
|
||||
hook = v()
|
||||
if name and hook.point() != name:
|
||||
continue
|
||||
|
||||
results.get(hook.point(), []).append(hook)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
HookFactory = HookManager("hook_type")
|
||||
@@ -0,0 +1,85 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
import abc
|
||||
|
||||
from aworld.core.context.base import Context
|
||||
from aworld.core.event.base import Message
|
||||
from aworld.models.model_response import ModelResponse
|
||||
|
||||
|
||||
class HookPoint:
|
||||
START = "start"
|
||||
FINISHED = "finished"
|
||||
ERROR = "error"
|
||||
PRE_LLM_CALL = "pre_llm_call"
|
||||
POST_LLM_CALL = "post_llm_call"
|
||||
OUTPUT_PROCESS = "output_process"
|
||||
|
||||
class Hook:
|
||||
"""Runner hook."""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def point(self):
|
||||
"""Hook point."""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def exec(self, message: Message, context: Context = None) -> Message:
|
||||
"""Execute hook function."""
|
||||
|
||||
|
||||
class StartHook(Hook):
|
||||
"""Process in the hook point of the start."""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def point(self):
|
||||
return HookPoint.START
|
||||
|
||||
|
||||
class FinishedHook(Hook):
|
||||
"""Process in the hook point of the finished."""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def point(self):
|
||||
return HookPoint.FINISHED
|
||||
|
||||
|
||||
class ErrorHook(Hook):
|
||||
"""Process in the hook point of the error."""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def point(self):
|
||||
return HookPoint.ERROR
|
||||
|
||||
class PreLLMCallHook(Hook):
|
||||
"""Process in the hook point of the pre_llm_call."""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def point(self):
|
||||
return HookPoint.PRE_LLM_CALL
|
||||
|
||||
class PostLLMCallHook(Hook):
|
||||
"""Process in the hook point of the post_llm_call."""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def point(self):
|
||||
return HookPoint.POST_LLM_CALL
|
||||
|
||||
class OutputProcessHook(Hook):
|
||||
"""Output process hook for processing output data for display."""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def point(self):
|
||||
return HookPoint.OUTPUT_PROCESS
|
||||
|
||||
def process_output_content(self, content: str) -> str:
|
||||
"""process output content
|
||||
|
||||
Args:
|
||||
content: original content
|
||||
|
||||
Returns:
|
||||
processed content
|
||||
"""
|
||||
return content
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
import re
|
||||
import copy
|
||||
from typing import Dict, Any, AsyncGenerator
|
||||
|
||||
from aworld.core.context.base import Context
|
||||
from aworld.core.event.base import Message
|
||||
from aworld.logs.util import logger
|
||||
from aworld.models.model_response import ModelResponse
|
||||
from aworld.output.base import Output, MessageOutput
|
||||
from aworld.runners.hook.hook_factory import HookFactory
|
||||
from aworld.runners.hook.hooks import OutputProcessHook
|
||||
from aworld.utils.common import convert_to_snake
|
||||
|
||||
|
||||
@HookFactory.register(name="ModelResponseProcessHook",
|
||||
desc="Process ModelResponse type messages before sending to frontend display")
|
||||
class ModelResponseProcessHook(OutputProcessHook):
|
||||
"""Process ModelResponse type messages before sending to frontend display"""
|
||||
|
||||
def name(self):
|
||||
return convert_to_snake("ModelResponseProcessHook")
|
||||
|
||||
async def exec(self, message: Message, context: Context = None) -> Message:
|
||||
"""Process ModelResponse type messages
|
||||
|
||||
Args:
|
||||
message: Message object
|
||||
context: Context object
|
||||
|
||||
Returns:
|
||||
Processed message object
|
||||
"""
|
||||
# Get payload
|
||||
if not message or not message.payload:
|
||||
return message
|
||||
|
||||
payload = message.payload
|
||||
|
||||
# Process different types of payload
|
||||
if isinstance(payload, ModelResponse):
|
||||
# Directly process ModelResponse type
|
||||
processed_payload = self.process_model_response(payload)
|
||||
message.payload = processed_payload
|
||||
|
||||
# Record processing results
|
||||
self._log_processing_result(payload, processed_payload, context)
|
||||
|
||||
elif isinstance(payload, MessageOutput) and hasattr(payload, 'source'):
|
||||
# Process ModelResponse in MessageOutput
|
||||
source = payload.source
|
||||
if isinstance(source, ModelResponse):
|
||||
processed_source = self.process_model_response(source)
|
||||
payload.source = processed_source
|
||||
|
||||
# Record processing results
|
||||
self._log_processing_result(source, processed_source, context)
|
||||
return message
|
||||
|
||||
def process_model_response(self, model_response: ModelResponse) -> ModelResponse:
|
||||
"""Process ModelResponse
|
||||
|
||||
Args:
|
||||
model_response: ModelResponse object
|
||||
|
||||
Returns:
|
||||
Processed ModelResponse object
|
||||
"""
|
||||
if not model_response:
|
||||
return model_response
|
||||
|
||||
# Create a new ModelResponse object to avoid modifying the original
|
||||
processed_response = copy.deepcopy(model_response)
|
||||
content = self.process_output_content(processed_response.content)
|
||||
processed_response.content = content
|
||||
return processed_response
|
||||
|
||||
def _log_processing_result(self, original: ModelResponse, processed: ModelResponse, context: Context = None):
|
||||
"""Record processing results
|
||||
|
||||
Args:
|
||||
original: Original ModelResponse
|
||||
processed: Processed ModelResponse
|
||||
context: Context object
|
||||
"""
|
||||
# Record content length before and after processing for analysis
|
||||
original_length = len(original.content) if original and original.content else 0
|
||||
processed_length = len(processed.content) if processed and processed.content else 0
|
||||
|
||||
# Save processing results to context for later retrieval
|
||||
if context:
|
||||
if not hasattr(context, 'hook_results'):
|
||||
context.hook_results = {}
|
||||
if not hasattr(context.hook_results, 'output_process'):
|
||||
context.hook_results.output_process = {}
|
||||
|
||||
# Save processing results
|
||||
context.hook_results.output_process = {
|
||||
'hook_name': self.name(),
|
||||
'original_length': original_length,
|
||||
'processed_length': processed_length,
|
||||
'removed_content': original_length - processed_length,
|
||||
'processed_at': context.get_current_timestamp() if hasattr(context, 'get_current_timestamp') else None,
|
||||
'processing_details': {
|
||||
'removed_html_tags': True,
|
||||
'removed_think_tags': True
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(f"ModelResponse processing result: Original length {original_length}, Processed length {processed_length}, Removed content {original_length - processed_length}")
|
||||
@@ -0,0 +1,41 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
|
||||
HOOK_TEMPLATE = """
|
||||
import traceback
|
||||
|
||||
from aworld.core.context.base import Context
|
||||
|
||||
from aworld.core.event.base import Message, Constants, TopicType
|
||||
from aworld.runners.hook.hooks import *
|
||||
from aworld.runners.hook.hook_factory import HookFactory
|
||||
from aworld.logs.util import logger
|
||||
|
||||
from aworld.utils.common import convert_to_snake
|
||||
|
||||
|
||||
@HookFactory.register(name="{name}",
|
||||
desc="{desc}")
|
||||
class {name}({point}Hook):
|
||||
def name(self):
|
||||
return convert_to_snake("{name}")
|
||||
|
||||
async def exec(self, message: Message) -> Message:
|
||||
{func_import}import {func}
|
||||
try:
|
||||
res = {func}(message)
|
||||
if not res:
|
||||
raise ValueError(f"{func} no result return.")
|
||||
return Message(payload=res,
|
||||
session_id=message.context.session_id,
|
||||
sender="{name}",
|
||||
category=Constants.TASK,
|
||||
topic="{topic}")
|
||||
except Exception as e:
|
||||
logger.error(traceback.format_exc())
|
||||
return Message(payload=str(e),
|
||||
session_id=message.context.session_id,
|
||||
sender="{name}",
|
||||
category=Constants.TASK,
|
||||
topic=TopicType.ERROR)
|
||||
"""
|
||||
@@ -0,0 +1,55 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
from typing import Callable, Any
|
||||
|
||||
from aworld.runners.hook.template import HOOK_TEMPLATE
|
||||
from aworld.utils.common import snake_to_camel
|
||||
|
||||
|
||||
def hook(hook_point: str, name: str = None):
|
||||
"""Hook decorator.
|
||||
|
||||
NOTE: Hooks can be annotated, but they need to comply with the protocol agreement.
|
||||
The input parameter of the hook function is `Message` type, and the @hook needs to specify `hook_point`.
|
||||
|
||||
Examples:
|
||||
>>> @hook(hook_point=HookPoint.ERROR)
|
||||
>>> def error_process(message: Message) -> Message | None:
|
||||
>>> print("process error")
|
||||
The function `error_process` will be executed when an error message appears in the task,
|
||||
you can choose return nothing or return a message.
|
||||
|
||||
Args:
|
||||
hook_point: Hook point that wants to process the message.
|
||||
name: Hook name.
|
||||
"""
|
||||
|
||||
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||
# converts python function into a hoop with associated hoop point
|
||||
func_import = func.__module__
|
||||
if func_import == '__main__':
|
||||
path = inspect.getsourcefile(func)
|
||||
package = path.replace(os.getcwd(), '').replace('.py', '')
|
||||
if package[0] == '/':
|
||||
package = package[1:]
|
||||
func_import = f"from {package} "
|
||||
else:
|
||||
func_import = f"from {func_import} "
|
||||
|
||||
real_name = name if name else func.__name__
|
||||
con = HOOK_TEMPLATE.format(func_import=func_import,
|
||||
func=func.__name__,
|
||||
point=snake_to_camel(hook_point),
|
||||
name=real_name,
|
||||
topic=hook_point,
|
||||
desc='')
|
||||
with open(f"{real_name}.py", 'w+') as write:
|
||||
write.writelines(con)
|
||||
importlib.import_module(real_name)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
Reference in New Issue
Block a user