mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
update experience maker
This commit is contained in:
parent
3fd1ce6c1a
commit
404a783419
19 changed files with 1841 additions and 25 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,5 +1,5 @@
|
|||
.vscode
|
||||
.env
|
||||
.env*
|
||||
.DS_Store
|
||||
.idea
|
||||
venv/
|
||||
|
|
|
|||
0
cookbook/__init__.py
Normal file
0
cookbook/__init__.py
Normal file
0
cookbook/appworld/__init__.py
Normal file
0
cookbook/appworld/__init__.py
Normal file
328
cookbook/appworld/agent.py
Normal file
328
cookbook/appworld/agent.py
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
import json
|
||||
import re
|
||||
import time
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from appworld import AppWorld, load_task_ids, evaluate_task
|
||||
from appworld.apps.model_lib import CachedDBHandler
|
||||
from appworld.task import Task
|
||||
from jinja2 import Template
|
||||
from loguru import logger
|
||||
from openai import OpenAI
|
||||
from tqdm import tqdm
|
||||
|
||||
from experiencemaker.utils.util_function import load_env_keys
|
||||
|
||||
load_env_keys("../../.env")
|
||||
|
||||
# This is a basic prompt template containing all the necessary onboarding information to solve AppWorld tasks. It explains the role of the agent and the supervisor, how to explore the API documentation, how to operate the interactive coding environment and call APIs via a simple task, and provides key instructions and disclaimers.
|
||||
|
||||
# You can adapt it as needed by your agent. You can also choose to bypass API docs app and build your own API retrieval, e.g., for FullCodeRefl, IPFunCall, etc, we asked an LLM to predict relevant APIs separately and put its documentation directly in the prompt.
|
||||
PROMPT_TEMPLATE = """
|
||||
USER:
|
||||
I am your supervisor and you are a super intelligent AI Assistant whose job is to achieve my day-to-day tasks completely autonomously.
|
||||
|
||||
To do this, you will need to interact with app/s (e.g., spotify, venmo, etc) using their associated APIs on my behalf. For this you will undertake a *multi-step conversation* using a python REPL environment. That is, you will write the python code and the environment will execute it and show you the result, based on which, you will write python code for the next step and so on, until you've achieved the goal. This environment will let you interact with app/s using their associated APIs on my behalf.
|
||||
|
||||
Here are three key APIs that you need to know to get more information
|
||||
|
||||
# To get a list of apps that are available to you.
|
||||
print(apis.api_docs.show_app_descriptions())
|
||||
|
||||
# To get the list of apis under any app listed above, e.g. supervisor
|
||||
print(apis.api_docs.show_api_descriptions(app_name='supervisor'))
|
||||
|
||||
# To get the specification of a particular api, e.g. supervisor app's show_account_passwords
|
||||
print(apis.api_docs.show_api_doc(app_name='supervisor', api_name='show_account_passwords'))
|
||||
|
||||
Each code execution will produce an output that you can use in subsequent calls. Using these APIs, you can now generate code, that the environment will execute, to solve the task.
|
||||
|
||||
For example, consider the task:
|
||||
|
||||
My name is: {{ supervisor.first_name }} {{ supervisor.last_name }}. My personal email is {{ supervisor.email }} and phone number is {{ supervisor.phone_number }}.
|
||||
|
||||
Task:
|
||||
|
||||
What is the password for my Spotify account?
|
||||
|
||||
ASSISTANT:
|
||||
# Okay. Lets first find which apps are available to get the password by looking at the app descriptions.
|
||||
print(apis.api_docs.show_app_descriptions())
|
||||
|
||||
USER:
|
||||
[
|
||||
{
|
||||
"name": "api_docs",
|
||||
"description": "An app to search and explore API documentation."
|
||||
},
|
||||
{
|
||||
"name": "supervisor",
|
||||
"description": "An app to access supervisor's personal information, account credentials, addresses, payment cards, and manage the assigned task."
|
||||
},
|
||||
...
|
||||
{
|
||||
"name": "spotify",
|
||||
"description": "A music streaming app to stream songs and manage song, album and playlist libraries."
|
||||
},
|
||||
{
|
||||
"name": "venmo",
|
||||
"description": "A social payment app to send, receive and request money to and from others."
|
||||
},
|
||||
...
|
||||
]
|
||||
|
||||
|
||||
ASSISTANT:
|
||||
# Looks like the supervisor app could help me with that. Lets see what apis are available under this app.
|
||||
print(apis.api_docs.show_api_descriptions(app_name='supervisor'))
|
||||
|
||||
|
||||
USER:
|
||||
[
|
||||
...
|
||||
"show_account_passwords : Show your supervisor's account passwords."
|
||||
...
|
||||
]
|
||||
|
||||
|
||||
ASSISTANT:
|
||||
# I can use `show_account_passwords` to get the passwords. Let me see its detailed specification to understand its arguments and output structure.
|
||||
print(apis.api_docs.show_api_doc(app_name='supervisor', api_name='show_account_passwords'))
|
||||
|
||||
USER:
|
||||
{
|
||||
'app_name': 'supervisor',
|
||||
'api_name': 'show_account_passwords',
|
||||
'path': '/account_passwords',
|
||||
'method': 'GET',
|
||||
'description': "Show your supervisor's app account passwords.",
|
||||
'parameters': [],
|
||||
'response_schemas': {
|
||||
'success': [{'account_name': 'string', 'password': 'string'}],
|
||||
'failure': {'message': 'string'}
|
||||
}
|
||||
}
|
||||
|
||||
ASSISTANT:
|
||||
# Okay, it requires no arguments. So I can just call it directly.
|
||||
print(apis.supervisor.show_account_passwords())
|
||||
|
||||
USER:
|
||||
[
|
||||
{
|
||||
"account_name": "spotify",
|
||||
"password": "dummy_spotify_pass"
|
||||
},
|
||||
{
|
||||
"account_name": "file_system",
|
||||
"password": "dummy_fs_pass"
|
||||
},
|
||||
...
|
||||
]
|
||||
|
||||
|
||||
ASSISTANT:
|
||||
# So the Spotify password is an entry in the `passwords` list with the account_name=spotify.
|
||||
spotify_password = [account_password["account_name"] == "spotify" for account_password in passwords][0]["password"]
|
||||
print(spotify_password)
|
||||
|
||||
|
||||
USER:
|
||||
dummy_spotify_pass
|
||||
|
||||
ASSISTANT:
|
||||
# When the task is completed, I need to call apis.supervisor.complete_task(). If there is an answer, I need to pass it as an argument `answer`. I will pass the spotify_password as an answer.
|
||||
apis.supervisor.complete_task(answer=spotify_password)
|
||||
|
||||
|
||||
USER:
|
||||
Marked the active task complete.
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
USER:
|
||||
**Key instructions and disclaimers**:
|
||||
|
||||
1. The email addresses, access tokens and variables (e.g. spotify_password) in the example above were only for demonstration. Obtain the correct information by calling relevant APIs yourself.
|
||||
2. Only generate valid code blocks, i.e., do not put them in ```...``` or add any extra formatting. Any thoughts should be put as code comments.
|
||||
3. You can use the variables from the previous code blocks in the subsequent code blocks.
|
||||
4. Write small chunks of code and only one chunk of code in every step. Make sure everything is working correctly before making any irreversible change.
|
||||
5. The provided Python environment has access to its standard library. But modules and functions that have a risk of affecting the underlying OS, file system or process are disabled. You will get an error if do call them.
|
||||
6. Any reference to a file system in the task instructions means the file system *app*, operable via given APIs, and not the actual file system the code is running on. So do not write code making calls to os-level modules and functions.
|
||||
7. To interact with apps, only use the provided APIs, and not the corresponding Python packages. E.g., do NOT use `spotipy` for Spotify. Remember, the environment only has the standard library.
|
||||
8. The provided API documentation has both the input arguments and the output JSON schemas. All calls to APIs and parsing its outputs must be as per this documentation.
|
||||
9. For APIs that return results in "pages", make sure to consider all pages.
|
||||
10. To obtain current date or time, use Python functions like `datetime.now()` or obtain it from the phone app. Do not rely on your existing knowledge of what the current date or time is.
|
||||
11. For all temporal requests, use proper time boundaries, e.g., if I ask for something that happened yesterday, make sure to consider the time between 00:00:00 and 23:59:59. All requests are concerning a single, default (no) time zone.
|
||||
12. Any reference to my friends, family or any other person or relation refers to the people in my phone's contacts list.
|
||||
13. All my personal information, and information about my app account credentials, physical addresses and owned payment cards are stored in the "supervisor" app. You can access them via the APIs provided by the supervisor app.
|
||||
14. Once you have completed the task, call `apis.supervisor.complete_task()`. If the task asks for some information, return it as the answer argument, i.e. call `apis.supervisor.complete_task(answer=<answer>)`. For tasks that do not require an answer, just skip the answer argument or pass it as None.
|
||||
15. The answers, when given, should be just entity or number, not full sentences, e.g., `answer=10` for "How many songs are in the Spotify queue?". When an answer is a number, it should be in numbers, not in words, e.g., "10" and not "ten".
|
||||
16. You can also pass `status="fail"` in the complete_task API if you are sure you cannot solve it and want to exit.
|
||||
17. You must make all decisions completely autonomously and not ask for any clarifications or confirmations from me or anyone else.
|
||||
|
||||
USER:
|
||||
Using these APIs, now generate code to solve the actual task:
|
||||
|
||||
My name is: {{ supervisor.first_name }} {{ supervisor.last_name }}. My personal email is {{ supervisor.email }} and phone number is {{ supervisor.phone_number }}.
|
||||
|
||||
Task:
|
||||
|
||||
{{ instruction }}
|
||||
"""
|
||||
|
||||
|
||||
class MinimalReactAgent:
|
||||
"""A minimal ReAct Agent for AppWorld tasks."""
|
||||
|
||||
def __init__(self, task: Task):
|
||||
self.task = task
|
||||
self.history: list[dict] = self.prompt_messages()
|
||||
|
||||
@staticmethod
|
||||
def call_llm(messages: list[dict]) -> str:
|
||||
for i in range(100):
|
||||
try:
|
||||
client = OpenAI()
|
||||
# Change this function to modify the base llm
|
||||
response = client.chat.completions.create(
|
||||
model="qwen-max-2025-01-25", messages=messages, temperature=0.6, max_tokens=400, seed=123
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
logger.exception("")
|
||||
time.sleep(1 + i * 10)
|
||||
|
||||
return "call llm error"
|
||||
|
||||
def prompt_messages(self) -> list[dict]:
|
||||
dictionary = {"supervisor": self.task.supervisor, "instruction": self.task.instruction}
|
||||
prompt = Template(PROMPT_TEMPLATE.lstrip()).render(dictionary)
|
||||
# Extract and return the OpenAI JSON formatted messages from the prompt
|
||||
messages: list[dict] = []
|
||||
last_start = 0
|
||||
for match in re.finditer("(USER|ASSISTANT|SYSTEM):\n", prompt):
|
||||
last_end = match.span()[0]
|
||||
if len(messages) == 0:
|
||||
if last_end != 0:
|
||||
raise ValueError(
|
||||
f"Start of the prompt has no assigned role: {prompt[:last_end]}"
|
||||
)
|
||||
else:
|
||||
messages[-1]["content"] = prompt[last_start:last_end]
|
||||
mesg_type = match.group(1).lower()
|
||||
messages.append({"role": mesg_type, "content": None})
|
||||
last_start = match.span()[1]
|
||||
messages[-1]["content"] = prompt[last_start:]
|
||||
return messages
|
||||
|
||||
def next_code_block(self, last_execution_output: str | None = None) -> str:
|
||||
if last_execution_output is not None:
|
||||
self.history.append({"role": "user", "content": last_execution_output})
|
||||
code = self.call_llm(self.history)
|
||||
self.history.append({"role": "assistant", "content": code})
|
||||
return code
|
||||
|
||||
|
||||
def run_one_agent(task_index: int, task_id: str, experiment_name: str, max_interactions: int = 50):
|
||||
with AppWorld(task_id=task_id, experiment_name=experiment_name) as world:
|
||||
print("instruction: " + world.task.instruction)
|
||||
agent = MinimalReactAgent(world.task)
|
||||
output: str | None = None
|
||||
messages: list = [{"supervisor": world.task.supervisor, "instruction": world.task.instruction}]
|
||||
path: Path = Path(f"./exp_result/{experiment_name}")
|
||||
|
||||
for i in range(max_interactions):
|
||||
code = agent.next_code_block(output)
|
||||
messages.append({"role": "assistant", "content": code})
|
||||
output = world.execute(code)
|
||||
if len(output) > 2000:
|
||||
output = output[:2000]
|
||||
messages.append({"role": "user", "content": output, "actual_size": len(output)})
|
||||
else:
|
||||
messages.append({"role": "user", "content": output})
|
||||
logger.info(f"task_index={task_index} task_id={task_id} steps={i}")
|
||||
|
||||
with open(path / f"{task_index}_{task_id}.jsonl", "w") as f:
|
||||
json.dump(messages, f, indent=2)
|
||||
|
||||
eval_result = world.evaluate().to_dict()
|
||||
logger.info(f"===== {i} {json.dumps(eval_result)}=====")
|
||||
|
||||
if world.task_completed():
|
||||
messages.append({"role": "task_completed", "content": "task_completed"})
|
||||
logger.info(f"task_index={task_index} task_id={task_id} complete.")
|
||||
break
|
||||
|
||||
with open(path / f"{task_index}_{task_id}.jsonl", "w") as f:
|
||||
json.dump(messages, f, indent=2)
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
def run_agent(dataset_name: str, max_workers: int = 1):
|
||||
experiment_name = "agent_" + dataset_name
|
||||
path: Path = Path(f"./exp_result/{experiment_name}")
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
task_ids = load_task_ids(dataset_name)
|
||||
with ProcessPoolExecutor(max_workers=max_workers) as executor:
|
||||
task_list: list = []
|
||||
for index, task_id in enumerate(task_ids):
|
||||
task = executor.submit(run_one_agent, task_index=index, task_id=task_id, experiment_name=experiment_name)
|
||||
task_list.append((task_id, task))
|
||||
time.sleep(1)
|
||||
|
||||
for i, (task_id, task) in enumerate(task_list):
|
||||
task.result()
|
||||
|
||||
|
||||
def eval_agent(dataset_name: str):
|
||||
experiment_name = "agent_" + dataset_name
|
||||
path: Path = Path(f"./exp_result/{experiment_name}")
|
||||
ratio_list = []
|
||||
success_list = []
|
||||
if not CachedDBHandler.is_empty():
|
||||
raise Exception(
|
||||
"The cached DB handler is not empty. You likely have an open AppWorld somewhere. "
|
||||
"Consider calling world.close() on the open one or AppWorld.close_all() to force "
|
||||
"close all."
|
||||
)
|
||||
|
||||
CachedDBHandler.reset()
|
||||
for file in tqdm(path.iterdir(), desc=experiment_name):
|
||||
if file.is_file() and file.suffix == ".jsonl":
|
||||
task_index, task_id = file.stem.split("_", 1)
|
||||
tracker = evaluate_task(
|
||||
task_id=task_id,
|
||||
experiment_name=experiment_name,
|
||||
suppress_errors=True,
|
||||
save_report=False)
|
||||
num_passes = len(tracker.passes)
|
||||
num_failures = len(tracker.failures)
|
||||
ratio: float = num_passes / (num_passes + num_failures)
|
||||
success: float = float(num_failures == 0)
|
||||
# logger.info(f"task_index={task_index} task_id={task_id} ratio={ratio} success={success}")
|
||||
|
||||
ratio_list.append(ratio)
|
||||
success_list.append(success)
|
||||
|
||||
CachedDBHandler.reset()
|
||||
|
||||
logger.info(f"experiment_name={experiment_name} size={len(ratio_list)} "
|
||||
f"ratio={np.mean(ratio_list)} success={np.mean(success_list)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# run_agent(dataset_name="train")
|
||||
# run_agent(dataset_name="dev")
|
||||
# run_agent(dataset_name="test_normal")
|
||||
# run_agent(dataset_name="test_challenge")
|
||||
|
||||
eval_agent(dataset_name="train")
|
||||
eval_agent(dataset_name="dev")
|
||||
eval_agent(dataset_name="test_normal")
|
||||
eval_agent(dataset_name="test_challenge")
|
||||
1
cookbook/financial_agent/__init__.py
Normal file
1
cookbook/financial_agent/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
__version__ = "0.1.0"
|
||||
81
cookbook/financial_agent/example.py
Normal file
81
cookbook/financial_agent/example.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
金融分析师Agent使用示例
|
||||
"""
|
||||
|
||||
import sys
|
||||
from experiencemaker.utils.util_function import load_env_keys
|
||||
|
||||
load_env_keys("../../.env")
|
||||
# 添加项目根目录到Python路径
|
||||
# project_root = Path(__file__).parent.parent
|
||||
# sys.path.insert(0, str(project_root))
|
||||
sys.path.append(".")
|
||||
from financial_agent import FinancialAgent
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 加载环境变量
|
||||
load_env_keys()
|
||||
|
||||
# 初始实体列表
|
||||
init_entity_list = [
|
||||
"美元债务",
|
||||
"美债利率",
|
||||
"美元指数",
|
||||
"工业金属银、铜、铝",
|
||||
"黄金",
|
||||
"稳定币",
|
||||
"石油",
|
||||
"能源",
|
||||
"军工",
|
||||
"海运",
|
||||
]
|
||||
|
||||
# 创建金融分析师Agent
|
||||
agent = FinancialAgent(
|
||||
model_name="qwen-max-2025-01-25",
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# 执行知识图谱构建
|
||||
output_file = "金融知识图谱.jsonl"
|
||||
stats = agent.execute(
|
||||
init_entity_list=init_entity_list,
|
||||
dump_file_path=output_file,
|
||||
max_iter=5, # 减少迭代次数用于演示
|
||||
search_strategy="mixed"
|
||||
)
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("执行结果统计:")
|
||||
print(f"总实体数: {stats['total_entities']}")
|
||||
print(f"总关系数: {stats['total_relations']}")
|
||||
print(f"新增关系数: {stats['new_relations']}")
|
||||
print(f"迭代次数: {stats['iterations']}")
|
||||
print("=" * 50)
|
||||
|
||||
# 查询示例
|
||||
print("\n查询示例:")
|
||||
query_results = agent.query_knowledge_graph("美元")
|
||||
print(f"包含'美元'的关系数量: {len(query_results)}")
|
||||
|
||||
# 获取特定实体的关系
|
||||
print("\n美元指数的关系:")
|
||||
usd_relations = agent.get_entity_relations("美元指数")
|
||||
for i, relation in enumerate(usd_relations[:3]): # 只显示前3个
|
||||
print(f"{i + 1}. {relation}")
|
||||
|
||||
# 生成可视化图表
|
||||
print("\n生成可视化图表...")
|
||||
agent.visualize("financial_knowledge_graph.html")
|
||||
|
||||
print(f"\n知识图谱已保存到: {output_file}")
|
||||
print("可视化图表已保存到: financial_knowledge_graph.html")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
345
cookbook/financial_agent/financial_agent.py
Normal file
345
cookbook/financial_agent/financial_agent.py
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
"""
|
||||
金融分析师Agent主类
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
from typing import List, Dict
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from experiencemaker.enumeration.role import Role
|
||||
from experiencemaker.model import OpenAICompatibleBaseLLM
|
||||
from experiencemaker.schema.trajectory import Message
|
||||
from experiencemaker.tool import CodeTool, DashscopeSearchTool
|
||||
from knowledge_graph import KnowledgeGraphBuilder
|
||||
from schema import FinancialRelation
|
||||
|
||||
|
||||
class FinancialAgent:
|
||||
"""金融分析师Agent"""
|
||||
|
||||
def __init__(self, model_name: str = "qwen-max-2025-01-25", verbose: bool = True):
|
||||
"""初始化金融分析师Agent"""
|
||||
self.llm = OpenAICompatibleBaseLLM(model_name=model_name)
|
||||
self.search_tool = DashscopeSearchTool()
|
||||
self.code_tool = CodeTool()
|
||||
self.tools = [self.search_tool, self.code_tool]
|
||||
self.verbose = verbose
|
||||
|
||||
# 知识图谱构建器
|
||||
self.knowledge_graph = KnowledgeGraphBuilder()
|
||||
|
||||
# 系统提示词
|
||||
self.system_prompt = """你是一个专业的金融分析师,专门负责分析金融市场中各种实体之间的逻辑关系。
|
||||
|
||||
你的任务:
|
||||
1. 通过搜索发现多个实体之间的逻辑关系
|
||||
2. 不断补充新的实体到实体列表中
|
||||
3. 将多跳的实体关系拆分成多个单跳的实体关系
|
||||
|
||||
实体关系格式:
|
||||
{
|
||||
"input_entities": ["实体1", "实体2"],
|
||||
"output_entities": ["实体3", "实体4"],
|
||||
"relation": "正向、负向、中性",
|
||||
"reasoning": "实体1和实体2是通过什么样的逻辑影响到实体3和实体4",
|
||||
"source": "来源",
|
||||
"confidence": "置信度(0-1)",
|
||||
"timestamp": "时间戳",
|
||||
}
|
||||
|
||||
请确保:
|
||||
- 关系描述准确、具体
|
||||
- 置信度合理评估
|
||||
- 来源信息完整
|
||||
- 时间戳格式:YYYY-MM-DD HH:MM:SS
|
||||
- 多跳关系要拆分成多个单跳关系
|
||||
"""
|
||||
|
||||
def _search_entity_relations(self, entity_list: List[str]) -> List[FinancialRelation]:
|
||||
"""搜索实体间的关系"""
|
||||
if self.verbose:
|
||||
logger.info(f"Searching relations for entities: {entity_list[:5]}...")
|
||||
|
||||
# 构建搜索查询
|
||||
entities_str = "、".join(entity_list[:5]) # 限制实体数量
|
||||
query = f"请分析以下金融实体之间的关系:{entities_str}。请详细说明它们之间的逻辑关系,包括正向、负向或中性的影响关系。"
|
||||
|
||||
# 使用搜索工具
|
||||
try:
|
||||
search_result = self.search_tool.execute(query=query)
|
||||
if self.verbose:
|
||||
logger.info(f"Search completed, result length: {len(str(search_result))}")
|
||||
except Exception as e:
|
||||
logger.error(f"Search failed: {e}")
|
||||
return []
|
||||
|
||||
# 使用LLM分析搜索结果并提取关系
|
||||
analysis_prompt = f"""
|
||||
基于以下搜索结果,请分析金融实体之间的关系:
|
||||
|
||||
搜索结果:
|
||||
{search_result[:10000]}
|
||||
|
||||
当前实体列表:
|
||||
{entity_list[:100]}
|
||||
|
||||
请提取出实体间的关系,格式如下(JSON格式):
|
||||
{{
|
||||
"relations": [
|
||||
{{
|
||||
"input_entities": ["实体1", "实体2"],
|
||||
"output_entities": ["实体3", "实体4"],
|
||||
"relation": "正向/负向/中性",
|
||||
"reasoning": "详细的分析逻辑",
|
||||
"source": "信息来源",
|
||||
"confidence": 0.8
|
||||
}}
|
||||
],
|
||||
"new_entities": ["新实体1", "新实体2"]
|
||||
}}
|
||||
|
||||
请确保:
|
||||
1. 关系描述准确具体
|
||||
2. 置信度在0-1之间
|
||||
3. 如果发现新的相关实体,请添加到new_entities中
|
||||
4. 只返回JSON格式,不要其他内容
|
||||
5. 多跳关系要拆分成多个单跳关系
|
||||
"""
|
||||
|
||||
messages = [
|
||||
Message(role=Role.SYSTEM, content=self.system_prompt),
|
||||
Message(role=Role.USER, content=analysis_prompt)
|
||||
]
|
||||
|
||||
try:
|
||||
response = self.llm.chat(messages, self.tools)
|
||||
|
||||
# 尝试解析JSON响应
|
||||
content = str(response.content)
|
||||
json_match = re.search(r'\{.*\}', content, re.DOTALL)
|
||||
if json_match:
|
||||
data = json.loads(json_match.group())
|
||||
|
||||
relations = []
|
||||
for rel_data in data.get("relations", []):
|
||||
try:
|
||||
relation = FinancialRelation(
|
||||
input_entities=rel_data["input_entities"],
|
||||
output_entities=rel_data["output_entities"],
|
||||
relation=rel_data["relation"],
|
||||
reasoning=rel_data["reasoning"],
|
||||
source=rel_data["source"],
|
||||
confidence=rel_data["confidence"],
|
||||
timestamp=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
relations.append(relation)
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to create relation: {e}, data: {rel_data}")
|
||||
|
||||
# 添加新实体
|
||||
for new_entity in data.get("new_entities", []):
|
||||
self.knowledge_graph.entities.add(new_entity)
|
||||
|
||||
if self.verbose:
|
||||
logger.info(
|
||||
f"Extracted {len(relations)} relations and {len(data.get('new_entities', []))} new entities")
|
||||
|
||||
return relations
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing LLM response: {e}")
|
||||
if self.verbose:
|
||||
logger.error(f"Response content: {response.content if 'response' in locals() else 'No response'}")
|
||||
|
||||
return []
|
||||
|
||||
def _search_specific_entity_pairs(self, entity_pairs: List[List[str]]) -> List[FinancialRelation]:
|
||||
"""搜索特定实体对之间的关系"""
|
||||
all_relations = []
|
||||
|
||||
for pair in entity_pairs:
|
||||
if len(pair) < 2:
|
||||
continue
|
||||
|
||||
query = f"请分析{pair[0]}和{pair[1]}之间的金融关系,包括它们如何相互影响,以及对其他金融实体的影响。"
|
||||
|
||||
try:
|
||||
search_result = self.search_tool.execute(query=query)
|
||||
|
||||
analysis_prompt = f"""
|
||||
基于搜索结果,分析{pair[0]}和{pair[1]}之间的关系:
|
||||
|
||||
搜索结果:
|
||||
{search_result[:800] if isinstance(search_result, str) else str(search_result)[:800]}
|
||||
|
||||
请提取关系,格式如下(JSON格式):
|
||||
{{
|
||||
"relations": [
|
||||
{{
|
||||
"input_entities": ["{pair[0]}", "{pair[1]}"],
|
||||
"output_entities": ["影响实体1", "影响实体2"],
|
||||
"relation": "正向/负向/中性",
|
||||
"reasoning": "详细分析",
|
||||
"source": "来源",
|
||||
"confidence": 0.8
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
只返回JSON格式。
|
||||
"""
|
||||
|
||||
messages = [
|
||||
Message(role=Role.SYSTEM, content=self.system_prompt),
|
||||
Message(role=Role.USER, content=analysis_prompt)
|
||||
]
|
||||
|
||||
response = self.llm.chat(messages, self.tools)
|
||||
|
||||
content = str(response.content)
|
||||
json_match = re.search(r'\{.*\}', content, re.DOTALL)
|
||||
if json_match:
|
||||
data = json.loads(json_match.group())
|
||||
|
||||
for rel_data in data.get("relations", []):
|
||||
try:
|
||||
relation = FinancialRelation(
|
||||
input_entities=rel_data["input_entities"],
|
||||
output_entities=rel_data["output_entities"],
|
||||
relation=rel_data["relation"],
|
||||
reasoning=rel_data["reasoning"],
|
||||
source=rel_data["source"],
|
||||
confidence=rel_data["confidence"],
|
||||
timestamp=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
all_relations.append(relation)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to create relation: {e}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching pair {pair}: {e}")
|
||||
|
||||
return all_relations
|
||||
|
||||
def _generate_entity_pairs(self, entity_list: List[str], max_pairs: int = 10) -> List[List[str]]:
|
||||
"""生成实体对用于搜索"""
|
||||
pairs = []
|
||||
entities = list(entity_list)
|
||||
|
||||
# 生成所有可能的二元组合
|
||||
for i in range(len(entities)):
|
||||
for j in range(i + 1, len(entities)):
|
||||
pairs.append([entities[i], entities[j]])
|
||||
if len(pairs) >= max_pairs:
|
||||
break
|
||||
if len(pairs) >= max_pairs:
|
||||
break
|
||||
|
||||
return pairs
|
||||
|
||||
def execute(self, init_entity_list: List[str], dump_file_path: str, max_iter: int = 10,
|
||||
search_strategy: str = "mixed") -> Dict:
|
||||
"""
|
||||
执行金融知识图谱构建
|
||||
|
||||
Args:
|
||||
init_entity_list: 初始实体列表
|
||||
dump_file_path: 输出文件路径
|
||||
max_iter: 最大迭代次数
|
||||
search_strategy: 搜索策略 ("general", "pairs", "mixed")
|
||||
|
||||
Returns:
|
||||
执行结果统计
|
||||
"""
|
||||
logger.info(f"Starting financial knowledge graph construction with {len(init_entity_list)} initial entities")
|
||||
|
||||
# 1. 初始化实体列表
|
||||
self.knowledge_graph.entities = set(init_entity_list)
|
||||
|
||||
# 2. 加载历史数据
|
||||
self.knowledge_graph.load_from_jsonl(dump_file_path)
|
||||
|
||||
# 3. 迭代搜索
|
||||
iteration = 0
|
||||
total_new_relations = 0
|
||||
|
||||
while iteration < max_iter:
|
||||
iteration += 1
|
||||
logger.info(f"Iteration {iteration}/{max_iter}")
|
||||
|
||||
new_relations_count = 0
|
||||
|
||||
if search_strategy in ["general", "mixed"]:
|
||||
# 通用搜索
|
||||
entity_list = list(self.knowledge_graph.entities)
|
||||
relations = self._search_entity_relations(entity_list)
|
||||
|
||||
for relation in relations:
|
||||
if self.knowledge_graph.add_relation(relation):
|
||||
new_relations_count += 1
|
||||
|
||||
if search_strategy in ["pairs", "mixed"] and iteration % 2 == 0:
|
||||
# 实体对搜索
|
||||
entity_list = list(self.knowledge_graph.entities)
|
||||
entity_pairs = self._generate_entity_pairs(entity_list, max_pairs=5)
|
||||
relations = self._search_specific_entity_pairs(entity_pairs)
|
||||
|
||||
for relation in relations:
|
||||
if self.knowledge_graph.add_relation(relation):
|
||||
new_relations_count += 1
|
||||
|
||||
total_new_relations += new_relations_count
|
||||
logger.info(f"Added {new_relations_count} new relations in iteration {iteration}")
|
||||
|
||||
# 检查是否还有新的关系可以添加
|
||||
if new_relations_count == 0:
|
||||
logger.info("No new relations found, stopping iteration")
|
||||
break
|
||||
|
||||
# 保存中间结果
|
||||
self.knowledge_graph.export_to_jsonl(dump_file_path)
|
||||
|
||||
# 4. 保存最终结果
|
||||
self.knowledge_graph.export_to_jsonl(dump_file_path)
|
||||
|
||||
# 5. 生成统计信息
|
||||
stats = self.knowledge_graph.get_entity_statistics()
|
||||
stats["new_relations"] = total_new_relations
|
||||
stats["iterations"] = iteration
|
||||
|
||||
logger.info(f"Financial knowledge graph construction completed!")
|
||||
logger.info(f"Total entities: {stats['total_entities']}")
|
||||
logger.info(f"Total relations: {stats['total_relations']}")
|
||||
logger.info(f"New relations added: {total_new_relations}")
|
||||
|
||||
return stats
|
||||
|
||||
def query_knowledge_graph(self, query: str) -> List[FinancialRelation]:
|
||||
"""查询知识图谱"""
|
||||
# 简单的关键词匹配查询
|
||||
query_lower = query.lower()
|
||||
results = []
|
||||
|
||||
for relation in self.knowledge_graph.relations:
|
||||
# 检查输入实体、输出实体、推理过程是否包含查询关键词
|
||||
all_text = " ".join([
|
||||
" ".join(relation.input_entities),
|
||||
" ".join(relation.output_entities),
|
||||
relation.reasoning,
|
||||
relation.source
|
||||
]).lower()
|
||||
|
||||
if query_lower in all_text:
|
||||
results.append(relation)
|
||||
|
||||
return results
|
||||
|
||||
def get_entity_relations(self, entity: str) -> List[FinancialRelation]:
|
||||
"""获取特定实体的所有关系"""
|
||||
return self.knowledge_graph.find_related_entities(entity)
|
||||
|
||||
def visualize(self, output_path: str = "financial_knowledge_graph.html"):
|
||||
"""生成可视化图表"""
|
||||
self.knowledge_graph.visualize_graph(output_path)
|
||||
238
cookbook/financial_agent/knowledge_graph.py
Normal file
238
cookbook/financial_agent/knowledge_graph.py
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
"""
|
||||
金融知识图谱构建器
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from typing import List, Set, Dict
|
||||
|
||||
from schema import FinancialRelation
|
||||
|
||||
|
||||
class KnowledgeGraphBuilder:
|
||||
"""金融知识图谱构建器"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化知识图谱构建器"""
|
||||
self.entities: Set[str] = set()
|
||||
self.relations: List[FinancialRelation] = []
|
||||
self.relation_hashes: Set[str] = set() # 用于去重
|
||||
self.entity_aliases: Dict[str, Set[str]] = {} # 实体别名映射
|
||||
|
||||
def _generate_relation_hash(self, relation: FinancialRelation) -> str:
|
||||
"""生成关系的哈希值用于去重"""
|
||||
content = f"{sorted(relation.input_entities)}_{sorted(relation.output_entities)}_{relation.relation}_{relation.reasoning}"
|
||||
return hashlib.md5(content.encode()).hexdigest()
|
||||
|
||||
def add_relation(self, relation: FinancialRelation) -> bool:
|
||||
"""添加关系,如果重复则返回False"""
|
||||
relation_hash = self._generate_relation_hash(relation)
|
||||
if relation_hash in self.relation_hashes:
|
||||
return False
|
||||
|
||||
self.relation_hashes.add(relation_hash)
|
||||
self.relations.append(relation)
|
||||
|
||||
# 添加新实体到实体列表
|
||||
for entity in relation.input_entities + relation.output_entities:
|
||||
self.entities.add(entity)
|
||||
|
||||
return True
|
||||
|
||||
def add_entity_alias(self, main_entity: str, aliases: List[str]):
|
||||
"""添加实体别名"""
|
||||
if main_entity not in self.entity_aliases:
|
||||
self.entity_aliases[main_entity] = set()
|
||||
|
||||
for alias in aliases:
|
||||
self.entity_aliases[main_entity].add(alias)
|
||||
self.entities.add(alias)
|
||||
|
||||
def get_entity_aliases(self, entity: str) -> Set[str]:
|
||||
"""获取实体的所有别名"""
|
||||
for main_entity, aliases in self.entity_aliases.items():
|
||||
if entity in aliases or entity == main_entity:
|
||||
return aliases | {main_entity}
|
||||
return {entity}
|
||||
|
||||
def find_related_entities(self, entity: str, max_depth: int = 2) -> List[FinancialRelation]:
|
||||
"""查找与指定实体相关的所有关系"""
|
||||
related_relations = []
|
||||
visited_entities = set()
|
||||
entities_to_check = {entity}
|
||||
|
||||
for depth in range(max_depth):
|
||||
current_entities = entities_to_check.copy()
|
||||
entities_to_check.clear()
|
||||
|
||||
for relation in self.relations:
|
||||
# 检查关系是否涉及当前实体
|
||||
relation_entities = set(relation.input_entities + relation.output_entities)
|
||||
if relation_entities & current_entities:
|
||||
related_relations.append(relation)
|
||||
# 添加新的实体到下一轮检查
|
||||
entities_to_check.update(relation_entities - visited_entities)
|
||||
|
||||
visited_entities.update(current_entities)
|
||||
|
||||
if not entities_to_check:
|
||||
break
|
||||
|
||||
return related_relations
|
||||
|
||||
def get_entity_statistics(self) -> Dict:
|
||||
"""获取实体统计信息"""
|
||||
return {
|
||||
"total_entities": len(self.entities),
|
||||
"total_relations": len(self.relations),
|
||||
"unique_relations": len(self.relation_hashes),
|
||||
"entity_aliases": len(self.entity_aliases)
|
||||
}
|
||||
|
||||
def export_to_jsonl(self, file_path: str):
|
||||
"""导出到JSONL文件"""
|
||||
# os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
for relation in self.relations:
|
||||
f.write(json.dumps(relation.to_dict(), ensure_ascii=False) + '\n')
|
||||
|
||||
def load_from_jsonl(self, file_path: str):
|
||||
"""从JSONL文件加载"""
|
||||
if not os.path.exists(file_path):
|
||||
return
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line:
|
||||
data = json.loads(line)
|
||||
relation = FinancialRelation.from_dict(data)
|
||||
self.add_relation(relation)
|
||||
|
||||
def export_to_networkx_format(self) -> Dict:
|
||||
"""导出为NetworkX可用的格式"""
|
||||
nodes = []
|
||||
edges = []
|
||||
|
||||
# 添加节点
|
||||
for entity in self.entities:
|
||||
nodes.append({
|
||||
"id": entity,
|
||||
"label": entity,
|
||||
"type": "entity"
|
||||
})
|
||||
|
||||
# 添加边
|
||||
for i, relation in enumerate(self.relations):
|
||||
for input_entity in relation.input_entities:
|
||||
for output_entity in relation.output_entities:
|
||||
edges.append({
|
||||
"source": input_entity,
|
||||
"target": output_entity,
|
||||
"relation": relation.relation,
|
||||
"reasoning": relation.reasoning,
|
||||
"confidence": relation.confidence,
|
||||
"source_info": relation.source,
|
||||
"timestamp": relation.timestamp
|
||||
})
|
||||
|
||||
return {
|
||||
"nodes": nodes,
|
||||
"edges": edges,
|
||||
"metadata": self.get_entity_statistics()
|
||||
}
|
||||
|
||||
def visualize_graph(self, output_path: str = "financial_knowledge_graph.html"):
|
||||
"""生成可视化图表(HTML格式)"""
|
||||
try:
|
||||
import plotly.graph_objects as go
|
||||
import networkx as nx
|
||||
|
||||
# 创建NetworkX图
|
||||
G = nx.DiGraph()
|
||||
|
||||
# 添加节点
|
||||
for entity in self.entities:
|
||||
G.add_node(entity)
|
||||
|
||||
# 添加边
|
||||
for relation in self.relations:
|
||||
for input_entity in relation.input_entities:
|
||||
for output_entity in relation.output_entities:
|
||||
G.add_edge(
|
||||
input_entity,
|
||||
output_entity,
|
||||
relation=relation.relation,
|
||||
confidence=relation.confidence
|
||||
)
|
||||
|
||||
# 使用spring布局
|
||||
pos = nx.spring_layout(G, k=1, iterations=50)
|
||||
|
||||
# 创建边轨迹
|
||||
edge_x = []
|
||||
edge_y = []
|
||||
edge_text = []
|
||||
|
||||
for edge in G.edges(data=True):
|
||||
x0, y0 = pos[edge[0]]
|
||||
x1, y1 = pos[edge[1]]
|
||||
edge_x.extend([x0, x1, None])
|
||||
edge_y.extend([y0, y1, None])
|
||||
edge_text.append(f"{edge[0]} → {edge[1]} ({edge[2]['relation']})")
|
||||
|
||||
edge_trace = go.Scatter(
|
||||
x=edge_x, y=edge_y,
|
||||
line=dict(width=0.5, color='#888'),
|
||||
hoverinfo='text',
|
||||
text=edge_text,
|
||||
mode='lines')
|
||||
|
||||
# 创建节点轨迹
|
||||
node_x = []
|
||||
node_y = []
|
||||
node_text = []
|
||||
|
||||
for node in G.nodes():
|
||||
x, y = pos[node]
|
||||
node_x.append(x)
|
||||
node_y.append(y)
|
||||
node_text.append(node)
|
||||
|
||||
node_trace = go.Scatter(
|
||||
x=node_x, y=node_y,
|
||||
mode='markers+text',
|
||||
hoverinfo='text',
|
||||
text=node_text,
|
||||
textposition="top center",
|
||||
marker=dict(
|
||||
showscale=True,
|
||||
colorscale='YlGnBu',
|
||||
size=10,
|
||||
color=[],
|
||||
line_width=2))
|
||||
|
||||
# 设置节点颜色
|
||||
node_adjacency_list = []
|
||||
for node in G.nodes():
|
||||
node_adjacency_list.append(len(list(G.neighbors(node))))
|
||||
node_trace.marker.color = node_adjacency_list
|
||||
|
||||
# 创建图形
|
||||
fig = go.Figure(data=[edge_trace, node_trace],
|
||||
layout=go.Layout(
|
||||
title='金融知识图谱',
|
||||
showlegend=False,
|
||||
hovermode='closest',
|
||||
margin=dict(b=20, l=5, r=5, t=40),
|
||||
xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
|
||||
yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))
|
||||
)
|
||||
|
||||
fig.write_html(output_path)
|
||||
print(f"知识图谱已保存到: {output_path}")
|
||||
|
||||
except ImportError:
|
||||
print("需要安装 plotly 和 networkx 来生成可视化图表")
|
||||
print("运行: pip install plotly networkx")
|
||||
46
cookbook/financial_agent/schema.py
Normal file
46
cookbook/financial_agent/schema.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""
|
||||
金融实体关系的schema定义
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass
|
||||
class FinancialRelation:
|
||||
"""金融实体关系的数据结构"""
|
||||
input_entities: List[str]
|
||||
output_entities: List[str]
|
||||
relation: str # "正向", "负向", "中性"
|
||||
reasoning: str
|
||||
source: str
|
||||
confidence: float
|
||||
timestamp: str
|
||||
|
||||
def __post_init__(self):
|
||||
"""初始化后的验证"""
|
||||
if not isinstance(self.confidence, (int, float)) or not 0 <= self.confidence <= 1:
|
||||
raise ValueError("confidence must be a float between 0 and 1")
|
||||
|
||||
if self.relation not in ["正向", "负向", "中性"]:
|
||||
raise ValueError("relation must be one of: 正向, 负向, 中性")
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典格式"""
|
||||
return {
|
||||
"input_entities": self.input_entities,
|
||||
"output_entities": self.output_entities,
|
||||
"relation": self.relation,
|
||||
"reasoning": self.reasoning,
|
||||
"source": self.source,
|
||||
"confidence": self.confidence,
|
||||
"timestamp": self.timestamp
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict):
|
||||
"""从字典创建实例"""
|
||||
return cls(**data)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.input_entities} -> {self.output_entities} ({self.relation}, 置信度: {self.confidence})"
|
||||
171
cookbook/financial_agent/test/README.md
Normal file
171
cookbook/financial_agent/test/README.md
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
# 金融分析师Agent
|
||||
|
||||
一个智能的金融分析师Agent,能够通过搜索和分析构建金融知识图谱,发现金融实体之间的逻辑关系。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🔍 **智能搜索**: 使用大模型和搜索工具发现金融实体关系
|
||||
- 🧠 **知识图谱构建**: 自动构建和更新金融知识图谱
|
||||
- 🔗 **关系分析**: 分析正向、负向、中性关系
|
||||
- 📊 **可视化**: 生成交互式知识图谱可视化
|
||||
- 🔄 **迭代优化**: 支持多轮迭代,不断丰富知识图谱
|
||||
- 📝 **数据导出**: 支持JSONL格式导出
|
||||
|
||||
## 安装
|
||||
|
||||
1. 克隆项目并安装依赖:
|
||||
```bash
|
||||
pip install -r financial_agent/requirements.txt
|
||||
```
|
||||
|
||||
2. 设置环境变量:
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_openai_api_key"
|
||||
export OPENAI_BASE_URL="your_openai_base_url"
|
||||
export DASHSCOPE_API_KEY="your_dashscope_api_key"
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 基本使用
|
||||
|
||||
```python
|
||||
from financial_agent import FinancialAgent
|
||||
|
||||
# 创建Agent
|
||||
agent = FinancialAgent(model_name="qwen-max-2025-01-25")
|
||||
|
||||
# 初始实体列表
|
||||
init_entities = ["美元债务", "美债利率", "美元指数", "黄金", "石油"]
|
||||
|
||||
# 执行知识图谱构建
|
||||
stats = agent.execute(
|
||||
init_entity_list=init_entities,
|
||||
dump_file_path="金融知识图谱.jsonl",
|
||||
max_iter=10,
|
||||
search_strategy="mixed"
|
||||
)
|
||||
|
||||
print(f"构建完成!总实体数: {stats['total_entities']}, 总关系数: {stats['total_relations']}")
|
||||
```
|
||||
|
||||
### 运行示例
|
||||
|
||||
```bash
|
||||
python financial_agent/example.py
|
||||
```
|
||||
|
||||
## 核心组件
|
||||
|
||||
### FinancialAgent
|
||||
|
||||
主要的金融分析师Agent类,负责:
|
||||
- 搜索和分析金融实体关系
|
||||
- 构建和更新知识图谱
|
||||
- 提供查询接口
|
||||
|
||||
### FinancialRelation
|
||||
|
||||
金融实体关系的数据结构:
|
||||
```python
|
||||
{
|
||||
"input_entities": ["实体1", "实体2"],
|
||||
"output_entities": ["实体3", "实体4"],
|
||||
"relation": "正向、负向、中性",
|
||||
"reasoning": "关系推理过程",
|
||||
"source": "信息来源",
|
||||
"confidence": "置信度(0-1)",
|
||||
"timestamp": "时间戳"
|
||||
}
|
||||
```
|
||||
|
||||
### KnowledgeGraphBuilder
|
||||
|
||||
知识图谱构建器,提供:
|
||||
- 关系去重和验证
|
||||
- 实体别名管理
|
||||
- 图谱查询和统计
|
||||
- 可视化生成
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 搜索策略
|
||||
|
||||
- `"general"`: 通用搜索,分析多个实体间的关系
|
||||
- `"pairs"`: 实体对搜索,专注于特定实体对
|
||||
- `"mixed"`: 混合策略,结合两种方法
|
||||
|
||||
### 模型配置
|
||||
|
||||
支持多种大模型:
|
||||
- `qwen-max-2025-01-25`
|
||||
- `qwen3-32b`
|
||||
- 其他OpenAI兼容模型
|
||||
|
||||
## 输出格式
|
||||
|
||||
### JSONL文件格式
|
||||
|
||||
每行一个JSON对象,包含完整的金融关系信息:
|
||||
|
||||
```jsonl
|
||||
{"input_entities": ["美债利率", "美元债务"], "output_entities": ["美元指数"], "relation": "负向", "reasoning": "美债利率上升和美元债务增加会导致美元走弱", "source": "金融分析报告", "confidence": 0.85, "timestamp": "2024-01-15 10:30:00"}
|
||||
{"input_entities": ["石油价格", "美元指数"], "output_entities": ["通胀预期"], "relation": "正向", "reasoning": "石油价格上涨和美元走弱会推高通胀预期", "source": "经济分析", "confidence": 0.78, "timestamp": "2024-01-15 10:31:00"}
|
||||
```
|
||||
|
||||
### 可视化输出
|
||||
|
||||
生成交互式HTML图表,支持:
|
||||
- 节点拖拽
|
||||
- 关系查看
|
||||
- 缩放和平移
|
||||
- 悬停信息显示
|
||||
|
||||
## API参考
|
||||
|
||||
### FinancialAgent.execute()
|
||||
|
||||
执行知识图谱构建:
|
||||
|
||||
```python
|
||||
def execute(self, init_entity_list: List[str], dump_file_path: str,
|
||||
max_iter: int = 10, search_strategy: str = "mixed") -> Dict
|
||||
```
|
||||
|
||||
### FinancialAgent.query_knowledge_graph()
|
||||
|
||||
查询知识图谱:
|
||||
|
||||
```python
|
||||
def query_knowledge_graph(self, query: str) -> List[FinancialRelation]
|
||||
```
|
||||
|
||||
### FinancialAgent.get_entity_relations()
|
||||
|
||||
获取特定实体的关系:
|
||||
|
||||
```python
|
||||
def get_entity_relations(self, entity: str) -> List[FinancialRelation]
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
1. **金融研究**: 自动发现金融市场中的实体关系
|
||||
2. **投资分析**: 分析投资标的之间的相互影响
|
||||
3. **风险管理**: 识别风险传导路径
|
||||
4. **政策分析**: 分析政策对市场的影响机制
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **API限制**: 注意搜索API的调用频率限制
|
||||
2. **数据质量**: 建议对生成的关系进行人工验证
|
||||
3. **成本控制**: 大模型调用会产生费用,注意控制迭代次数
|
||||
4. **环境配置**: 确保正确配置API密钥和基础URL
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交Issue和Pull Request来改进项目!
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
0
cookbook/financial_agent/test/__init__.py
Normal file
0
cookbook/financial_agent/test/__init__.py
Normal file
77
cookbook/financial_agent/test/prompt.py
Normal file
77
cookbook/financial_agent/test/prompt.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
entity_list = [
|
||||
"美元债务",
|
||||
"美债利率",
|
||||
"美元指数",
|
||||
"工业金属银、铜、铝",
|
||||
"黄金",
|
||||
"稳定币",
|
||||
"石油",
|
||||
"能源",
|
||||
"军工",
|
||||
"海运",
|
||||
]
|
||||
|
||||
|
||||
|
||||
"""
|
||||
现在初始状态你有以下的实体列表:
|
||||
|
||||
美元债务
|
||||
美债利率
|
||||
美元指数
|
||||
工业金属银、铜、铝
|
||||
黄金
|
||||
稳定币
|
||||
石油
|
||||
能源
|
||||
军工
|
||||
海运
|
||||
|
||||
|
||||
你有一个大模型调用的Client
|
||||
from experiencemaker.model import OpenAICompatibleBaseLLM
|
||||
|
||||
你有两个工具:
|
||||
1. 代码执行工具
|
||||
from experiencemaker.tool import CodeTool
|
||||
2. web search工具
|
||||
from experiencemaker.tool import DashscopeSearchTool
|
||||
|
||||
请你根据以上内容,设计一个金融分析师的Agent,并给出Agent的代码。
|
||||
Agent的任务:
|
||||
1. 通过不断的搜索,发现多个实体之间的逻辑关系,例如“美债利率高”、“美元债务高”会让美元变弱,从而导致“美元指数”变弱。
|
||||
2. 同时通过不断的搜索,不断的补充新的实体到**实体列表**。
|
||||
3. 多跳的实体关系,可以拆成多个单跳的实体关系。
|
||||
|
||||
实体关系的schema如下:
|
||||
{
|
||||
"input_entities": ["实体1", "实体2"],
|
||||
"output_entities": ["实体3", "实体4"],
|
||||
"relation": "正向、负向、中性",
|
||||
"reasoning": "实体1和实体2是通过什么样的逻辑影响到实体3和实体4",
|
||||
"source": "来源",
|
||||
"confidence": "置信度",
|
||||
"timestamp": "时间戳",
|
||||
}
|
||||
|
||||
最后这些schema的list会变成一个jsonl文件,文件名是:
|
||||
金融知识图谱.jsonl
|
||||
|
||||
```python
|
||||
class FinAgent(object):
|
||||
|
||||
def execute(self, init_entity_list: list[str], dump_file_path: str, max_iter: int = 100):
|
||||
# init_entity_list 是初始的实体列表
|
||||
# dump_file_path 是最终的jsonl文件路径
|
||||
# max_iter 是最大迭代次数
|
||||
|
||||
# 1. 初始化实体列表
|
||||
# 2. 读取dump_file_path中历史的金融知识图谱(如果有),加载到内容
|
||||
# 2. 迭代搜索,不断更新已有的知识图谱,不断补充新的知识图谱,不断增加新的实体(和已有的语义去重)
|
||||
# 3. 迭代到max_iter次,或者没有新的知识图谱可以补充,则停止迭代, 保存到dump_file_path中
|
||||
pass
|
||||
```
|
||||
|
||||
帮忙把整个project写一下
|
||||
"""
|
||||
294
cookbook/financial_agent/test/test_financial_agent.py
Normal file
294
cookbook/financial_agent/test/test_financial_agent.py
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
金融分析师Agent测试脚本
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import datetime
|
||||
from typing import List, Dict, Set
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
from experiencemaker.model import OpenAICompatibleBaseLLM
|
||||
from experiencemaker.tool import CodeTool, DashscopeSearchTool
|
||||
from experiencemaker.schema.trajectory import Message
|
||||
from experiencemaker.enumeration.role import Role
|
||||
|
||||
|
||||
@dataclass
|
||||
class FinancialRelation:
|
||||
"""金融实体关系的数据结构"""
|
||||
input_entities: List[str]
|
||||
output_entities: List[str]
|
||||
relation: str # "正向", "负向", "中性"
|
||||
reasoning: str
|
||||
source: str
|
||||
confidence: float
|
||||
timestamp: str
|
||||
|
||||
|
||||
class FinancialAgent:
|
||||
"""金融分析师Agent"""
|
||||
|
||||
def __init__(self, model_name: str = "qwen-max-2025-01-25"):
|
||||
"""初始化金融分析师Agent"""
|
||||
self.llm = OpenAICompatibleBaseLLM(model_name=model_name)
|
||||
self.search_tool = DashscopeSearchTool()
|
||||
self.code_tool = CodeTool()
|
||||
self.tools = [self.search_tool, self.code_tool]
|
||||
|
||||
# 存储实体和关系
|
||||
self.entities: Set[str] = set()
|
||||
self.relations: List[FinancialRelation] = []
|
||||
self.relation_hashes: Set[str] = set() # 用于去重
|
||||
|
||||
# 系统提示词
|
||||
self.system_prompt = """你是一个专业的金融分析师,专门负责分析金融市场中各种实体之间的逻辑关系。
|
||||
|
||||
你的任务:
|
||||
1. 通过搜索发现多个实体之间的逻辑关系
|
||||
2. 不断补充新的实体到实体列表中
|
||||
3. 将多跳的实体关系拆分成多个单跳的实体关系
|
||||
|
||||
实体关系格式:
|
||||
{
|
||||
"input_entities": ["实体1", "实体2"],
|
||||
"output_entities": ["实体3", "实体4"],
|
||||
"relation": "正向、负向、中性",
|
||||
"reasoning": "实体1和实体2是通过什么样的逻辑影响到实体3和实体4",
|
||||
"source": "来源",
|
||||
"confidence": "置信度(0-1)",
|
||||
"timestamp": "时间戳",
|
||||
}
|
||||
|
||||
请确保:
|
||||
- 关系描述准确、具体
|
||||
- 置信度合理评估
|
||||
- 来源信息完整
|
||||
- 时间戳格式:YYYY-MM-DD HH:MM:SS
|
||||
"""
|
||||
|
||||
def _generate_relation_hash(self, relation: FinancialRelation) -> str:
|
||||
"""生成关系的哈希值用于去重"""
|
||||
content = f"{sorted(relation.input_entities)}_{sorted(relation.output_entities)}_{relation.relation}_{relation.reasoning}"
|
||||
return hashlib.md5(content.encode()).hexdigest()
|
||||
|
||||
def _add_relation(self, relation: FinancialRelation) -> bool:
|
||||
"""添加关系,如果重复则返回False"""
|
||||
relation_hash = self._generate_relation_hash(relation)
|
||||
if relation_hash in self.relation_hashes:
|
||||
return False
|
||||
|
||||
self.relation_hashes.add(relation_hash)
|
||||
self.relations.append(relation)
|
||||
|
||||
# 添加新实体到实体列表
|
||||
for entity in relation.input_entities + relation.output_entities:
|
||||
self.entities.add(entity)
|
||||
|
||||
return True
|
||||
|
||||
def _load_existing_relations(self, file_path: str):
|
||||
"""加载已存在的关系数据"""
|
||||
if not os.path.exists(file_path):
|
||||
return
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line:
|
||||
data = json.loads(line)
|
||||
relation = FinancialRelation(**data)
|
||||
self._add_relation(relation)
|
||||
print(f"Loaded {len(self.relations)} existing relations from {file_path}")
|
||||
except Exception as e:
|
||||
print(f"Error loading existing relations: {e}")
|
||||
|
||||
def _save_relations(self, file_path: str):
|
||||
"""保存关系到文件"""
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
for relation in self.relations:
|
||||
f.write(json.dumps(relation.__dict__, ensure_ascii=False) + '\n')
|
||||
print(f"Saved {len(self.relations)} relations to {file_path}")
|
||||
|
||||
def _search_entity_relations(self, entity_list: List[str]) -> List[FinancialRelation]:
|
||||
"""搜索实体间的关系"""
|
||||
# 构建搜索查询
|
||||
entities_str = "、".join(entity_list[:5]) # 限制实体数量
|
||||
query = f"请分析以下金融实体之间的关系:{entities_str}。请详细说明它们之间的逻辑关系,包括正向、负向或中性的影响关系。"
|
||||
|
||||
# 使用搜索工具
|
||||
search_result = self.search_tool.execute(query=query)
|
||||
|
||||
# 使用LLM分析搜索结果并提取关系
|
||||
analysis_prompt = f"""
|
||||
基于以下搜索结果,请分析金融实体之间的关系:
|
||||
|
||||
搜索结果:
|
||||
{search_result[:1000] if isinstance(search_result, str) else str(search_result)[:1000]} # 限制长度
|
||||
|
||||
当前实体列表:
|
||||
{entity_list[:10]} # 限制显示数量
|
||||
|
||||
请提取出实体间的关系,格式如下(JSON格式):
|
||||
{{
|
||||
"relations": [
|
||||
{{
|
||||
"input_entities": ["实体1", "实体2"],
|
||||
"output_entities": ["实体3", "实体4"],
|
||||
"relation": "正向/负向/中性",
|
||||
"reasoning": "详细的分析逻辑",
|
||||
"source": "信息来源",
|
||||
"confidence": 0.8
|
||||
}}
|
||||
],
|
||||
"new_entities": ["新实体1", "新实体2"]
|
||||
}}
|
||||
|
||||
请确保:
|
||||
1. 关系描述准确具体
|
||||
2. 置信度在0-1之间
|
||||
3. 如果发现新的相关实体,请添加到new_entities中
|
||||
4. 只返回JSON格式,不要其他内容
|
||||
"""
|
||||
|
||||
messages = [
|
||||
Message(role=Role.SYSTEM, content=self.system_prompt),
|
||||
Message(role=Role.USER, content=analysis_prompt)
|
||||
]
|
||||
|
||||
response = self.llm._chat(messages, self.tools)
|
||||
|
||||
try:
|
||||
# 尝试解析JSON响应
|
||||
content = str(response.content)
|
||||
json_match = re.search(r'\{.*\}', content, re.DOTALL)
|
||||
if json_match:
|
||||
data = json.loads(json_match.group())
|
||||
|
||||
relations = []
|
||||
for rel_data in data.get("relations", []):
|
||||
relation = FinancialRelation(
|
||||
input_entities=rel_data["input_entities"],
|
||||
output_entities=rel_data["output_entities"],
|
||||
relation=rel_data["relation"],
|
||||
reasoning=rel_data["reasoning"],
|
||||
source=rel_data["source"],
|
||||
confidence=rel_data["confidence"],
|
||||
timestamp=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
relations.append(relation)
|
||||
|
||||
# 添加新实体
|
||||
for new_entity in data.get("new_entities", []):
|
||||
self.entities.add(new_entity)
|
||||
|
||||
return relations
|
||||
except Exception as e:
|
||||
print(f"Error parsing LLM response: {e}")
|
||||
print(f"Response content: {response.content}")
|
||||
|
||||
return []
|
||||
|
||||
def execute(self, init_entity_list: List[str], dump_file_path: str, max_iter: int = 5):
|
||||
"""
|
||||
执行金融知识图谱构建
|
||||
|
||||
Args:
|
||||
init_entity_list: 初始实体列表
|
||||
dump_file_path: 输出文件路径
|
||||
max_iter: 最大迭代次数
|
||||
"""
|
||||
print(f"Starting financial knowledge graph construction with {len(init_entity_list)} initial entities")
|
||||
|
||||
# 1. 初始化实体列表
|
||||
self.entities = set(init_entity_list)
|
||||
|
||||
# 2. 加载历史数据
|
||||
self._load_existing_relations(dump_file_path)
|
||||
|
||||
# 3. 迭代搜索
|
||||
iteration = 0
|
||||
new_relations_count = 0
|
||||
|
||||
while iteration < max_iter:
|
||||
iteration += 1
|
||||
print(f"Iteration {iteration}/{max_iter}")
|
||||
|
||||
# 搜索实体间关系
|
||||
entity_list = list(self.entities)
|
||||
relations = self._search_entity_relations(entity_list)
|
||||
|
||||
# 添加新关系
|
||||
added_count = 0
|
||||
for relation in relations:
|
||||
if self._add_relation(relation):
|
||||
added_count += 1
|
||||
|
||||
new_relations_count += added_count
|
||||
print(f"Added {added_count} new relations in iteration {iteration}")
|
||||
|
||||
# 检查是否还有新的关系可以添加
|
||||
if added_count == 0:
|
||||
print("No new relations found, stopping iteration")
|
||||
break
|
||||
|
||||
# 保存中间结果
|
||||
if iteration % 2 == 0:
|
||||
self._save_relations(dump_file_path)
|
||||
|
||||
# 4. 保存最终结果
|
||||
self._save_relations(dump_file_path)
|
||||
|
||||
print(f"Financial knowledge graph construction completed!")
|
||||
print(f"Total entities: {len(self.entities)}")
|
||||
print(f"Total relations: {len(self.relations)}")
|
||||
print(f"New relations added: {new_relations_count}")
|
||||
|
||||
return {
|
||||
"entities": list(self.entities),
|
||||
"relations": [rel.__dict__ for rel in self.relations],
|
||||
"total_entities": len(self.entities),
|
||||
"total_relations": len(self.relations),
|
||||
"new_relations": new_relations_count
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
from experiencemaker.utils.util_function import load_env_keys
|
||||
|
||||
# 加载环境变量
|
||||
load_env_keys()
|
||||
|
||||
# 初始实体列表
|
||||
init_entities = [
|
||||
"美元债务",
|
||||
"美债利率",
|
||||
"美元指数",
|
||||
"黄金",
|
||||
"石油",
|
||||
]
|
||||
|
||||
# 创建Agent并执行
|
||||
agent = FinancialAgent()
|
||||
result = agent.execute(
|
||||
init_entity_list=init_entities,
|
||||
dump_file_path="test/金融知识图谱.jsonl",
|
||||
max_iter=3 # 减少迭代次数用于测试
|
||||
)
|
||||
|
||||
print("=" * 50)
|
||||
print("构建完成!")
|
||||
print(f"总实体数: {result['total_entities']}")
|
||||
print(f"总关系数: {result['total_relations']}")
|
||||
print(f"新增关系数: {result['new_relations']}")
|
||||
print("=" * 50)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
191
cookbook/financial_agent/test/test_simple.py
Normal file
191
cookbook/financial_agent/test/test_simple.py
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
简化的金融Agent测试脚本
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# 模拟环境变量(如果没有设置的话)
|
||||
if not os.getenv("OPENAI_API_KEY"):
|
||||
os.environ["OPENAI_API_KEY"] = "test_key"
|
||||
if not os.getenv("OPENAI_BASE_URL"):
|
||||
os.environ["OPENAI_BASE_URL"] = "http://localhost:8000/v1"
|
||||
if not os.getenv("DASHSCOPE_API_KEY"):
|
||||
os.environ["DASHSCOPE_API_KEY"] = "test_key"
|
||||
|
||||
from financial_agent.schema import FinancialRelation
|
||||
from financial_agent.knowledge_graph import KnowledgeGraphBuilder
|
||||
|
||||
|
||||
def test_schema():
|
||||
"""测试FinancialRelation schema"""
|
||||
print("测试FinancialRelation schema...")
|
||||
|
||||
# 创建测试关系
|
||||
relation = FinancialRelation(
|
||||
input_entities=["美债利率", "美元债务"],
|
||||
output_entities=["美元指数"],
|
||||
relation="负向",
|
||||
reasoning="美债利率上升和美元债务增加会导致美元走弱",
|
||||
source="金融分析报告",
|
||||
confidence=0.85,
|
||||
timestamp="2024-01-15 10:30:00"
|
||||
)
|
||||
|
||||
print(f"创建的关系: {relation}")
|
||||
print(f"转换为字典: {relation.to_dict()}")
|
||||
|
||||
# 测试验证
|
||||
try:
|
||||
invalid_relation = FinancialRelation(
|
||||
input_entities=["实体1"],
|
||||
output_entities=["实体2"],
|
||||
relation="无效关系", # 应该报错
|
||||
reasoning="测试",
|
||||
source="测试",
|
||||
confidence=0.5,
|
||||
timestamp="2024-01-15 10:30:00"
|
||||
)
|
||||
except ValueError as e:
|
||||
print(f"验证错误(预期): {e}")
|
||||
|
||||
print("Schema测试通过!\n")
|
||||
|
||||
|
||||
def test_knowledge_graph():
|
||||
"""测试KnowledgeGraphBuilder"""
|
||||
print("测试KnowledgeGraphBuilder...")
|
||||
|
||||
# 创建知识图谱
|
||||
kg = KnowledgeGraphBuilder()
|
||||
|
||||
# 添加关系
|
||||
relation1 = FinancialRelation(
|
||||
input_entities=["美债利率", "美元债务"],
|
||||
output_entities=["美元指数"],
|
||||
relation="负向",
|
||||
reasoning="美债利率上升和美元债务增加会导致美元走弱",
|
||||
source="金融分析报告",
|
||||
confidence=0.85,
|
||||
timestamp="2024-01-15 10:30:00"
|
||||
)
|
||||
|
||||
relation2 = FinancialRelation(
|
||||
input_entities=["石油价格", "美元指数"],
|
||||
output_entities=["通胀预期"],
|
||||
relation="正向",
|
||||
reasoning="石油价格上涨和美元走弱会推高通胀预期",
|
||||
source="经济分析",
|
||||
confidence=0.78,
|
||||
timestamp="2024-01-15 10:31:00"
|
||||
)
|
||||
|
||||
# 添加关系
|
||||
kg.add_relation(relation1)
|
||||
kg.add_relation(relation2)
|
||||
|
||||
# 添加实体别名
|
||||
kg.add_entity_alias("美元指数", ["USD Index", "DXY"])
|
||||
|
||||
# 测试统计
|
||||
stats = kg.get_entity_statistics()
|
||||
print(f"知识图谱统计: {stats}")
|
||||
|
||||
# 测试查询
|
||||
usd_relations = kg.find_related_entities("美元指数")
|
||||
print(f"美元指数相关关系数量: {len(usd_relations)}")
|
||||
|
||||
# 测试导出
|
||||
test_file = "test_knowledge_graph.jsonl"
|
||||
kg.export_to_jsonl(test_file)
|
||||
print(f"知识图谱已导出到: {test_file}")
|
||||
|
||||
# 测试加载
|
||||
new_kg = KnowledgeGraphBuilder()
|
||||
new_kg.load_from_jsonl(test_file)
|
||||
print(f"加载后的实体数量: {len(new_kg.entities)}")
|
||||
|
||||
# 清理测试文件
|
||||
if os.path.exists(test_file):
|
||||
os.remove(test_file)
|
||||
|
||||
print("KnowledgeGraphBuilder测试通过!\n")
|
||||
|
||||
|
||||
def test_mock_agent():
|
||||
"""测试模拟的Agent(不调用真实API)"""
|
||||
print("测试模拟Agent...")
|
||||
|
||||
# 创建模拟的搜索结果
|
||||
mock_search_result = """
|
||||
根据最新金融分析,美债利率上升和美元债务增加会对美元指数产生负面影响。
|
||||
当美债利率上升时,投资者会要求更高的收益率,这可能导致美元走弱。
|
||||
同时,美元债务的增加也会增加市场对美元贬值的担忧。
|
||||
"""
|
||||
|
||||
# 模拟LLM响应
|
||||
mock_llm_response = {
|
||||
"relations": [
|
||||
{
|
||||
"input_entities": ["美债利率", "美元债务"],
|
||||
"output_entities": ["美元指数"],
|
||||
"relation": "负向",
|
||||
"reasoning": "美债利率上升和美元债务增加会导致美元走弱",
|
||||
"source": "金融分析报告",
|
||||
"confidence": 0.85
|
||||
}
|
||||
],
|
||||
"new_entities": ["美联储政策", "市场情绪"]
|
||||
}
|
||||
|
||||
print(f"模拟搜索结果: {mock_search_result[:100]}...")
|
||||
print(f"模拟LLM响应: {json.dumps(mock_llm_response, ensure_ascii=False, indent=2)}")
|
||||
|
||||
# 测试关系创建
|
||||
try:
|
||||
relation = FinancialRelation(
|
||||
input_entities=mock_llm_response["relations"][0]["input_entities"],
|
||||
output_entities=mock_llm_response["relations"][0]["output_entities"],
|
||||
relation=mock_llm_response["relations"][0]["relation"],
|
||||
reasoning=mock_llm_response["relations"][0]["reasoning"],
|
||||
source=mock_llm_response["relations"][0]["source"],
|
||||
confidence=mock_llm_response["relations"][0]["confidence"],
|
||||
timestamp="2024-01-15 10:30:00"
|
||||
)
|
||||
print(f"成功创建关系: {relation}")
|
||||
except Exception as e:
|
||||
print(f"创建关系失败: {e}")
|
||||
|
||||
print("模拟Agent测试通过!\n")
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("=" * 50)
|
||||
print("金融Agent测试开始")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
test_schema()
|
||||
test_knowledge_graph()
|
||||
test_mock_agent()
|
||||
|
||||
print("=" * 50)
|
||||
print("所有测试通过!")
|
||||
print("=" * 50)
|
||||
|
||||
except Exception as e:
|
||||
print(f"测试失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -3,7 +3,6 @@ from pydantic import Field, model_validator
|
|||
|
||||
from cookbook.simple_agent.your_own_agent import YourOwnAgent
|
||||
from experiencemaker.em_client import EMClient
|
||||
from experiencemaker.model import OpenAICompatibleBaseLLM
|
||||
from experiencemaker.schema.request import ContextGeneratorRequest, SummarizerRequest
|
||||
from experiencemaker.schema.response import ContextGeneratorResponse, SummarizerResponse
|
||||
from experiencemaker.schema.trajectory import Trajectory
|
||||
|
|
@ -52,8 +51,20 @@ class YourOwnAgentEnhanced(YourOwnAgent):
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
agent = YourOwnAgentEnhanced(workspace_id="w_agent_enhanced",
|
||||
llm=OpenAICompatibleBaseLLM(model_name="qwen3-32b", temperature=0.00001))
|
||||
traj = agent.execute()
|
||||
logger.info(traj.model_dump_json(indent=2))
|
||||
# agent = YourOwnAgentEnhanced(workspace_id="w_agent_enhanced",
|
||||
# llm=OpenAICompatibleBaseLLM(model_name="qwen3-32b", temperature=0.00001))
|
||||
# traj = agent.execute()
|
||||
# logger.info(traj.model_dump_json(indent=2))
|
||||
|
||||
em_client = EMClient(base_url="http://0.0.0.0:8001")
|
||||
|
||||
|
||||
request: ContextGeneratorRequest = ContextGeneratorRequest(trajectory=Trajectory(query="hello"),
|
||||
workspace_id="w123")
|
||||
response = em_client.call_summarizer()
|
||||
print(response)
|
||||
|
||||
|
||||
request: ContextGeneratorRequest = ContextGeneratorRequest(trajectory=Trajectory(query="hello"), workspace_id="w123")
|
||||
response = em_client.call_context_generator(request=request)
|
||||
print(response)
|
||||
|
|
|
|||
|
|
@ -19,17 +19,28 @@ class ExperienceFunction(BaseModel):
|
|||
func_args: List[ExperienceFunctionArg] = Field(default_factory=list, description="function arguments")
|
||||
|
||||
|
||||
class Experience(BaseModel):
|
||||
class FunctionExperience(BaseModel):
|
||||
experience_function: ExperienceFunction | None = Field(default=None, description="experience function(optional)")
|
||||
# 前期作为文本放进来,后期会转换成function
|
||||
|
||||
class TextExperience(BaseModel):
|
||||
experience_id: str = Field(default_factory=lambda: uuid4().hex, description="experience unique id")
|
||||
experience_workspace_id: str = Field(default="", description="unique workspace id")
|
||||
experience_role: str = Field(default="", description="experience role")
|
||||
experience_desc: str = Field(default="", description="use condition/purpose. It will be used in vector matching")
|
||||
|
||||
when_to_use_experience: str = Field(default="", description="use condition/purpose. It will be used in vector matching")
|
||||
experience_content: str | bytes = Field(default="", description="content of the experience")
|
||||
experience_function: ExperienceFunction | None = Field(default=None, description="experience function(optional)")
|
||||
experience_score: float = Field(default=0.0, description="score of the experience")
|
||||
|
||||
|
||||
metadata: dict = Field(default_factory=dict, description="additional metadata")
|
||||
"""
|
||||
metadata
|
||||
experience_created_time: str = Field(default_factory=lambda: datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
experience_modified_time: str = Field(default_factory=lambda: datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
metadata: dict = Field(default_factory=dict, description="additional metadata")
|
||||
experience_role: str = Field(default="", description="experience role")
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def to_vector_store_node(self) -> VectorStoreNode:
|
||||
metadata: dict = {
|
||||
|
|
|
|||
|
|
@ -11,14 +11,30 @@ class BaseRequest(BaseModel, ABC):
|
|||
workspace_id: str = Field(default="")
|
||||
|
||||
|
||||
class AgentWrapperRequest(BaseRequest):
|
||||
query: str = Field(default="")
|
||||
|
||||
# class AgentWrapperRequest(BaseRequest):
|
||||
# query: str = Field(default="")
|
||||
|
||||
# Experience retriever
|
||||
class ContextGeneratorRequest(BaseRequest):
|
||||
trajectory: Trajectory = Field(default_factory=Trajectory)
|
||||
messages: List[dict] = Field(default_factory=list)
|
||||
query: str = Field(default="")
|
||||
retrieve_top_k: int = Field(default=1)
|
||||
|
||||
|
||||
# Experience summarizer
|
||||
class SummarizerRequest(BaseRequest):
|
||||
trajectories: List[Trajectory] = Field(default_factory=dict)
|
||||
messages_list: List[List[dict]] | List[dict] = Field(default_factory=list)
|
||||
scores: List[float] | float = Field(default_factory=list, description="scores 要看summarizer对于score的定义")
|
||||
summarizer_config: str = "如何summary "
|
||||
|
||||
"""
|
||||
class DB相关操作
|
||||
|
||||
清空db
|
||||
db复制
|
||||
|
||||
1. 新增es数据库
|
||||
2. cursor帮忙写两个
|
||||
3. 导出功能,上传Experience
|
||||
|
||||
"""
|
||||
#
|
||||
|
|
@ -11,14 +11,20 @@ class BaseResponse(BaseModel, ABC):
|
|||
success: bool = Field(default=True)
|
||||
metadata: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class AgentWrapperResponse(BaseResponse):
|
||||
trajectory: Trajectory = Field(default_factory=Trajectory)
|
||||
#
|
||||
# class AgentWrapperResponse(BaseResponse):
|
||||
# trajectory: Trajectory = Field(default_factory=Trajectory)
|
||||
|
||||
|
||||
class ContextGeneratorResponse(BaseResponse):
|
||||
context_msg: ContextMessage = Field(default_factory=ContextMessage)
|
||||
experience: list[dict] = Field(default_factory=list)
|
||||
|
||||
merge_experience: str = Field(default="")
|
||||
|
||||
# when to use, experience, response
|
||||
|
||||
|
||||
|
||||
|
||||
class SummarizerResponse(BaseResponse):
|
||||
experiences: List[Experience] = Field(default_factory=list)
|
||||
experiences: List[dict] = Field(default_factory=list)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ class Reward(BaseModel):
|
|||
description: str = Field(default="Outcome 1 denotes success, and 0 denotes failure.")
|
||||
metadata: dict = Field(default_factory=dict)
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
return self.outcome > 0
|
||||
# @property
|
||||
# def success(self) -> bool:
|
||||
# return self.outcome > 0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue