refactor(reme_ai): formatting and minor code improvements

- Removed unnecessary comments
- Improved code formatting across multiple files
- Updated import statements and removed unused imports
- Refactored some function definitions for better readability
This commit is contained in:
jinli.yl 2025-08-31 15:49:16 +08:00
parent 72db149964
commit 0c2fb9a522
40 changed files with 854 additions and 516 deletions

View file

@ -1,26 +0,0 @@
[
{
"workspace_id": "test_workspace4",
"memory_id": "922a556d988f47bcadd8228d9f2235dc",
"memory_type": "task",
"when_to_use": "When conducting a company analysis requiring multi-dimensional insights (e.g., financials, market position, strategic shifts)",
"content": "Split the analysis into 3-4 distinct subtopics (e.g., financial performance, market challenges, strategic initiatives) and perform targeted web searches for each. Use language matching the user's query (Chinese in this case) to ensure relevant results. Avoid broad single searches that may miss critical angles.",
"score": 0.0,
"time_created": "2025-08-29 01:44:37",
"time_modified": "2025-08-29 01:44:37",
"author": "qwen3-30b-a3b-thinking-2507",
"metadata": {}
},
{
"workspace_id": "test_workspace4",
"memory_id": "5cac948c7e8141dfb7723ab482d3aa8c",
"memory_type": "task",
"when_to_use": "When verifying if retrieved information is sufficient to answer the user's query",
"content": "Always cross-check that the retrieved data covers all core aspects of the query before terminating. In this case, the financial metrics, market challenges, and strategic pivots were all covered in the search results, making further searches unnecessary. Never assume coverage—explicitly validate completeness.",
"score": 0.0,
"time_created": "2025-08-29 01:44:37",
"time_modified": "2025-08-29 01:44:37",
"author": "qwen3-30b-a3b-thinking-2507",
"metadata": {}
}
]

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,123 @@
{
"answer": "",
"messages": [],
"success": true,
"metadata": {
"memory_list": [
{
"workspace_id": "personal_memory_demo",
"memory_id": "42f9d6e7e8c34310b284d36cfb73a68d",
"memory_type": "personal",
"when_to_use": "John Smith, 28, San Francisco, tech company",
"content": "user's name is John Smith, he is 28 years old, and works at a tech company in San Francisco",
"score": 0.0,
"time_created": "2025-08-31 15:45:33",
"time_modified": "2025-08-31 15:45:33",
"author": "qwen3-30b-a3b-instruct-2507",
"metadata": {
"keywords": "John Smith, 28, San Francisco, tech company",
"source_message": "My name is John Smith, I'm 28 years old, and I work at a tech company in San Francisco",
"observation_type": "personal_info"
},
"target": "user",
"reflection_subject": ""
},
{
"workspace_id": "personal_memory_demo",
"memory_id": "9be49b1479e540c7abf0257595e49972",
"memory_type": "personal",
"when_to_use": "software engineer, backend, Python, Go",
"content": "user is a software engineer specializing in backend development using Python and Go",
"score": 0.0,
"time_created": "2025-08-31 15:45:33",
"time_modified": "2025-08-31 15:45:33",
"author": "qwen3-30b-a3b-instruct-2507",
"metadata": {
"keywords": "software engineer, backend, Python, Go",
"source_message": "I'm a software engineer, mainly doing backend development using Python and Go",
"observation_type": "personal_info"
},
"target": "user",
"reflection_subject": ""
},
{
"workspace_id": "personal_memory_demo",
"memory_id": "5287138b20ff432a920cd07d0e034579",
"memory_type": "personal",
"when_to_use": "basketball, sci-fi movies, Dune Part 2",
"content": "user enjoys playing basketball and watching sci-fi movies, recently watched Dune Part 2",
"score": 0.0,
"time_created": "2025-08-31 15:45:33",
"time_modified": "2025-08-31 15:45:33",
"author": "qwen3-30b-a3b-instruct-2507",
"metadata": {
"keywords": "basketball, sci-fi movies, Dune Part 2",
"source_message": "I enjoy playing basketball and watching sci-fi movies. I recently watched Dune Part 2",
"observation_type": "personal_info"
},
"target": "user",
"reflection_subject": ""
},
{
"workspace_id": "personal_memory_demo",
"memory_id": "c8f329903b82404c9d36e8a61bb30d67",
"memory_type": "personal",
"when_to_use": "cat, Shadow, pet",
"content": "user has a 3-year-old cat named Shadow",
"score": 0.0,
"time_created": "2025-08-31 15:45:33",
"time_modified": "2025-08-31 15:45:33",
"author": "qwen3-30b-a3b-instruct-2507",
"metadata": {
"keywords": "cat, Shadow, pet",
"source_message": "I have a cat named Shadow who is 3 years old",
"observation_type": "personal_info"
},
"target": "user",
"reflection_subject": ""
},
{
"workspace_id": "personal_memory_demo",
"memory_id": "7166549431bf4f019acb8331377f7def",
"memory_type": "personal",
"when_to_use": "Japanese cuisine, sushi, ramen",
"content": "user is interested in Japanese cuisine, especially sushi and ramen",
"score": 0.0,
"time_created": "2025-08-31 15:45:33",
"time_modified": "2025-08-31 15:45:33",
"author": "qwen3-30b-a3b-instruct-2507",
"metadata": {
"keywords": "Japanese cuisine, sushi, ramen",
"source_message": "I'm really interested in Japanese cuisine, especially sushi and ramen",
"observation_type": "personal_info"
},
"target": "user",
"reflection_subject": ""
},
{
"workspace_id": "personal_memory_demo",
"memory_id": "4ab4fe610ce845b6b174d03449df2881",
"memory_type": "personal",
"when_to_use": "Japan, Tokyo, Kyoto, trip",
"content": "user is planning a trip to Japan in September 2025, mainly to Tokyo and Kyoto",
"score": 0.0,
"time_created": "2025-08-31 15:45:32",
"time_modified": "2025-08-31 15:45:32",
"author": "qwen3-30b-a3b-instruct-2507",
"metadata": {
"keywords": "Japan, Tokyo, Kyoto, trip",
"time_info": "September 2025",
"source_message": "I'm planning a trip to Japan next month, mainly to Tokyo and Kyoto",
"observation_type": "personal_info_with_time"
},
"target": "user",
"reflection_subject": ""
}
],
"deleted_memory_ids": [],
"update_result": {
"deleted_count": 0,
"inserted_count": 6
}
}
}

View file

@ -1,108 +0,0 @@
import json
import requests
from dotenv import load_dotenv
load_dotenv()
base_url = "http://0.0.0.0:8002/"
workspace_id = "test_workspace4"
def run_agent(query: str, dump_messages: bool = False):
response = requests.post(url=base_url + "react", json={"query": query})
if response.status_code != 200:
print(response.text)
return []
response = response.json()
answer = response["answer"]
print(answer)
messages = response["messages"]
if dump_messages:
with open("messages.jsonl", "w") as f:
f.write(json.dumps(messages, indent=2, ensure_ascii=False))
return messages
def run_summary(messages: list, enable_dump_memory: bool = True):
response = requests.post(url=base_url + "summary_task_memory_simple", json={
"workspace_id": workspace_id,
"trajectories": [
{"messages": messages, "score": 1.0}
]
})
if response.status_code != 200:
print(response.text)
return
response = response.json()
memory_list = response["metadata"]["memory_list"]
if enable_dump_memory:
with open("memory.jsonl", "w") as f:
f.write(json.dumps(memory_list, indent=2, ensure_ascii=False))
def run_retrieve(query: str):
response = requests.post(url=base_url + "retrieve_task_memory_simple", json={
"workspace_id": workspace_id,
"query": query,
})
if response.status_code != 200:
print(response.text)
return ""
response = response.json()
answer: str = response["answer"]
print(f"answer={answer}")
return answer
def run_agent_with_memory(query_first: str, query_second: str, enable_dump_memory: bool = True):
messages = run_agent(query=query_second)
run_summary(messages, enable_dump_memory)
retrieved_memory = run_retrieve(query_first)
messages = run_agent(query=f"{retrieved_memory}\n\nUser Question:\n{query_first}")
return messages
def dump_memory():
response = requests.post(url=base_url + "vector_store", json={
"workspace_id": workspace_id,
"action": "dump",
"path": "./",
})
if response.status_code != 200:
print(response.text)
return
print(response.json())
def load_memory():
response = requests.post(url=base_url + "vector_store", json={
"workspace_id": "test_workspace2",
"action": "load",
"path": "./",
})
if response.status_code != 200:
print(response.text)
return
print(response.json())
if __name__ == "__main__":
query1 = "Analyze Xiaomi Corporation"
query2 = "Analyze the company Tesla."
run_agent(query=query1, dump_messages=True)
run_agent_with_memory(query_first=query1, query_second=query2)
dump_memory()
load_memory()

View file

@ -0,0 +1,78 @@
[
{
"workspace_id": "test_workspace",
"memory_id": "4ff6a7a783a84940a046e557de7542f0",
"memory_type": "task",
"when_to_use": "When analyzing a complex organization with multiple dimensions (history, products, finances, leadership), especially when the query is open-ended or broad like 'analyze [company]'",
"content": "The agent successfully decomposed the task into four targeted web searches: company overview/history, products/services, financial performance, and leadership impact. This multi-angle approach ensured comprehensive coverage of key domains. Each search was designed to extract distinct, high-value information layers, enabling synthesis into a structured, well-rounded analysis. The use of varied queries prevented redundancy and maximized information diversity.",
"score": 0.92,
"time_created": "2025-08-31 15:42:44",
"time_modified": "2025-08-31 15:42:44",
"author": "qwen3-30b-a3b-instruct-2507",
"metadata": {
"when_to_use": "When analyzing a complex organization with multiple dimensions (history, products, finances, leadership), especially when the query is open-ended or broad like 'analyze [company]'",
"experience": "The agent successfully decomposed the task into four targeted web searches: company overview/history, products/services, financial performance, and leadership impact. This multi-angle approach ensured comprehensive coverage of key domains. Each search was designed to extract distinct, high-value information layers, enabling synthesis into a structured, well-rounded analysis. The use of varied queries prevented redundancy and maximized information diversity.",
"tags": [
"multi-faceted analysis",
"information decomposition",
"web_search strategy",
"comprehensive coverage"
],
"confidence": 0.9,
"step_type": "reasoning",
"tools_used": [
"web_search"
]
}
},
{
"workspace_id": "test_workspace",
"memory_id": "2ad5b6aae8724e87944e69ea4476a97c",
"memory_type": "task",
"when_to_use": "When initial results from a general search are insufficient or too broad, and deeper insights are needed on specific subtopics such as financial trends, product portfolios, or leadership dynamics",
"content": "The agent proactively called the same tool (web_search) multiple times with different, focused queries to gather information from various perspectives. This iterative technique allowed for layered understanding—e.g., separating historical milestones from current financial challenges and leadership influence. It demonstrated strategic use of repetition not as redundancy but as a method to triangulate data across domains, significantly improving depth and accuracy.",
"score": 0.85,
"time_created": "2025-08-31 15:42:44",
"time_modified": "2025-08-31 15:42:44",
"author": "qwen3-30b-a3b-instruct-2507",
"metadata": {
"when_to_use": "When initial results from a general search are insufficient or too broad, and deeper insights are needed on specific subtopics such as financial trends, product portfolios, or leadership dynamics",
"experience": "The agent proactively called the same tool (web_search) multiple times with different, focused queries to gather information from various perspectives. This iterative technique allowed for layered understanding—e.g., separating historical milestones from current financial challenges and leadership influence. It demonstrated strategic use of repetition not as redundancy but as a method to triangulate data across domains, significantly improving depth and accuracy.",
"tags": [
"iterative querying",
"perspective diversification",
"information triangulation",
"search optimization"
],
"confidence": 0.85,
"step_type": "action",
"tools_used": [
"web_search"
]
}
},
{
"workspace_id": "test_workspace",
"memory_id": "87afc1367c6c4b739bbe191e1f47c875",
"memory_type": "task",
"when_to_use": "After gathering raw data from external sources, when synthesizing findings into a coherent, structured response that balances factual reporting with critical insight",
"content": "The agent effectively integrated results from multiple searches into a unified summary with clear sectioning: history, products, finance, and leadership. It highlighted both strengths (innovation, mission-driven vision) and risks (financial decline, leadership dependency). This synthesis transformed fragmented data into actionable intelligence, demonstrating how structured summarization enhances usability and decision-making value in agent outputs.",
"score": 0.85,
"time_created": "2025-08-31 15:42:44",
"time_modified": "2025-08-31 15:42:44",
"author": "qwen3-30b-a3b-instruct-2507",
"metadata": {
"when_to_use": "After gathering raw data from external sources, when synthesizing findings into a coherent, structured response that balances factual reporting with critical insight",
"experience": "The agent effectively integrated results from multiple searches into a unified summary with clear sectioning: history, products, finance, and leadership. It highlighted both strengths (innovation, mission-driven vision) and risks (financial decline, leadership dependency). This synthesis transformed fragmented data into actionable intelligence, demonstrating how structured summarization enhances usability and decision-making value in agent outputs.",
"tags": [
"synthesis",
"structured summarization",
"critical insight",
"data integration"
],
"confidence": 0.9,
"step_type": "observation",
"tools_used": []
}
}
]

View file

@ -0,0 +1,85 @@
import asyncio
import json
import aiohttp
# API base URL
base_url = "http://0.0.0.0:8002"
async def main():
# Create a unique workspace ID
workspace_id = "personal_memory_demo"
async with aiohttp.ClientSession() as session:
# Step 1: Clear existing memories in the workspace
print("Clearing existing memories...")
async with session.post(
f"{base_url}/vector_store",
json={
"action": "delete",
"workspace_id": workspace_id,
},
headers={"Content-Type": "application/json"}
) as response:
result = await response.json()
print(json.dumps(result, ensure_ascii=False))
# Step 2: Create a conversation with rich personal information
print("\nCreating conversation with personal information...")
messages = [
{"role": "user", "content": "My name is John Smith, I'm 28 years old, and I work at a tech company in San Francisco"},
{"role": "assistant", "content": "Nice to meet you, John!"},
{"role": "user", "content": "I'm a software engineer, mainly doing backend development using Python and Go"},
{"role": "assistant", "content": "I see, you're a backend engineer working with Python and Go."},
{"role": "user", "content": "I enjoy playing basketball and watching sci-fi movies. I recently watched Dune Part 2"},
{"role": "assistant", "content": "Basketball and sci-fi movies are great hobbies! Dune Part 2 was indeed amazing."},
{"role": "user", "content": "I have a cat named Shadow who is 3 years old"},
{"role": "assistant", "content": "Shadow sounds adorable! 3-year-old cats are quite playful."},
{"role": "user", "content": "I'm planning a trip to Japan next month, mainly to Tokyo and Kyoto"},
{"role": "assistant", "content": "Your Japan trip sounds exciting! Tokyo and Kyoto are both wonderful destinations with their own unique charm."},
{"role": "user", "content": "I'm really interested in Japanese cuisine, especially sushi and ramen"},
{"role": "assistant", "content": "Japanese cuisine is delicious! Sushi and ramen are very popular choices."},
]
# Step 3: Summarize personal memories from the conversation
print("\nSummarizing personal memories...")
async with session.post(
f"{base_url}/summary_personal_memory",
json={
"messages": messages,
"workspace_id": workspace_id,
},
headers={"Content-Type": "application/json"}
) as response:
result = await response.json()
print(json.dumps(result, ensure_ascii=False, indent=2))
# Wait for the memories to be processed and stored
print("\nWaiting for memories to be processed...")
await asyncio.sleep(2)
# Step 4: Retrieve personal memories with different queries
queries = [
"What's my name and age?",
"What do I do for work?",
"What are my hobbies?",
"Do I have any pets?",
"What are my travel plans?",
"What foods do I like?"
]
print("\nRetrieving personal memories...")
for query in queries:
print(f"\nQuery: {query}")
async with session.post(
f"{base_url}/retrieve_personal_memory",
json={
"query": query,
"workspace_id": workspace_id,
},
headers={"Content-Type": "application/json"}
) as response:
result = await response.json()
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
asyncio.run(main())

View file

@ -0,0 +1,291 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Task Memory Demo for MemoryScope
This script demonstrates how to use the task memory capabilities of MemoryScope.
It shows how to run an agent, summarize conversations, retrieve memories, and
manage the memory workspace.
"""
import json
import time
from typing import List, Dict, Any, Optional
import requests
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# API configuration
BASE_URL = "http://0.0.0.0:8002/"
WORKSPACE_ID = "test_workspace"
def handle_api_response(response: requests.Response) -> Optional[Dict[str, Any]]:
"""
Handle API response with proper error checking
Args:
response: Response object from requests
Returns:
Response JSON if successful, None otherwise
"""
if response.status_code != 200:
print(f"Error: {response.status_code}")
print(response.text)
return None
return response.json()
def delete_workspace() -> None:
"""
Delete the current workspace from the vector store
Returns:
None
"""
response = requests.post(
url=f"{BASE_URL}vector_store",
json={
"workspace_id": WORKSPACE_ID,
"action": "delete",
}
)
result = handle_api_response(response)
if result:
print(f"Workspace '{WORKSPACE_ID}' deleted successfully")
def run_agent(query: str, dump_messages: bool = False) -> List[Dict[str, Any]]:
"""
Run the agent with a specific query
Args:
query: The query to send to the agent
dump_messages: Whether to save messages to a file
Returns:
List of message objects from the conversation
"""
response = requests.post(
url=f"{BASE_URL}react",
json={"query": query}
)
result = handle_api_response(response)
if not result:
return []
# Extract and display the answer
answer = result.get("answer", "")
print(f"Agent response: {answer}")
# Get the conversation messages
messages = result.get("messages", [])
# Optionally save messages to file
if dump_messages and messages:
with open("messages.jsonl", "w") as f:
f.write(json.dumps(messages, indent=2, ensure_ascii=False))
print(f"Messages saved to messages.jsonl")
return messages
def run_summary(messages: List[Dict[str, Any]], enable_dump_memory: bool = True) -> None:
"""
Generate a summary of conversation messages and create task memories
Args:
messages: List of message objects from a conversation
enable_dump_memory: Whether to save memory list to a file
Returns:
None
"""
if not messages:
print("No messages to summarize")
return
response = requests.post(
# url=f"{BASE_URL}summary_task_memory_simple",
url=f"{BASE_URL}summary_task_memory",
json={
"workspace_id": WORKSPACE_ID,
"trajectories": [
{"messages": messages, "score": 1.0}
]
}
)
result = handle_api_response(response)
if not result:
return
# Extract memory list from response
memory_list = result.get("metadata", {}).get("memory_list", [])
print(f"Memory list: {memory_list}")
# Optionally save memory list to file
if enable_dump_memory and memory_list:
with open("memory.jsonl", "w") as f:
f.write(json.dumps(memory_list, indent=2, ensure_ascii=False))
print(f"Memory saved to memory.jsonl")
def run_retrieve(query: str) -> str:
"""
Retrieve relevant task memories based on a query
Args:
query: The query to retrieve relevant memories
Returns:
String containing the retrieved memory answer
"""
response = requests.post(
# url=f"{BASE_URL}retrieve_task_memory_simple",
url=f"{BASE_URL}retrieve_task_memory",
json={
"workspace_id": WORKSPACE_ID,
"query": query,
}
)
result = handle_api_response(response)
if not result:
return ""
# Extract and return the answer
answer = result.get("answer", "")
print(f"Retrieved memory: {answer}")
return answer
def run_agent_with_memory(query_first: str, query_second: str, enable_dump_memory: bool = True) -> List[Dict[str, Any]]:
"""
Run the agent with memory augmentation
This function demonstrates how to use task memory to enhance agent responses:
1. First run the agent with the second query to build memory
2. Then summarize the conversation to create memories
3. Retrieve relevant memories for the first query
4. Run the agent with the first query augmented with retrieved memories
Args:
query_first: The query to run with memory augmentation
query_second: The query to build initial memories
enable_dump_memory: Whether to save memory list to a file
Returns:
List of message objects from the final conversation
"""
# Run agent with second query to build initial memories
print(f"\n--- Building memories with query: '{query_second}' ---")
messages = run_agent(query=query_second)
# Summarize conversation to create memories
print("\n--- Summarizing conversation to create memories ---")
run_summary(messages, enable_dump_memory)
time.sleep(1)
# Retrieve relevant memories for the first query
print(f"\n--- Retrieving memories for query: '{query_first}' ---")
retrieved_memory = run_retrieve(query_first)
# Run agent with first query augmented with retrieved memories
print(f"\n--- Running agent with memory-augmented query ---")
augmented_query = f"{retrieved_memory}\n\nUser Question:\n{query_first}"
print(f"Augmented query: {augmented_query}")
messages = run_agent(query=augmented_query)
return messages
def dump_memory(path: str = "./") -> None:
"""
Dump the vector store memories to disk
Args:
path: Directory path to save the memories
Returns:
None
"""
response = requests.post(
url=f"{BASE_URL}vector_store",
json={
"workspace_id": WORKSPACE_ID,
"action": "dump",
"path": path,
}
)
result = handle_api_response(response)
if result:
print(f"Memory dumped to {path}")
def load_memory(path: str = "./") -> None:
"""
Load memories from disk into the vector store
Args:
path: Directory path to load the memories from
Returns:
None
"""
response = requests.post(
url=f"{BASE_URL}vector_store",
json={
"workspace_id": WORKSPACE_ID,
"action": "load",
"path": path,
}
)
result = handle_api_response(response)
if result:
print(f"Memory loaded from {path}")
def main() -> None:
"""
Main function to demonstrate task memory workflow
"""
# Define example queries
query1 = "Analyze Xiaomi Corporation"
query2 = "Analyze the company Tesla."
print("=== Task Memory Demo ===")
# Step 1: Clean up workspace
print("\n1. Deleting workspace...")
delete_workspace()
# Step 2: Run agent with first query and save messages
print("\n2. Running agent with first query...")
run_agent(query=query1, dump_messages=True)
# Step 3: Demonstrate memory-augmented agent
print("\n3. Running memory-augmented agent workflow...")
run_agent_with_memory(query_first=query1, query_second=query2)
# Step 4: Demonstrate memory persistence
print("\n4. Dumping memory to disk...")
dump_memory()
print("\n5. Loading memory from disk...")
load_memory()
print("\n=== Demo Complete ===")
if __name__ == "__main__":
main()

View file

@ -1,11 +1,9 @@
OPENAI_API_KEY=sk-xxxx
OPENAI_BASE_URL=https://xxxx/v1
FLOW_EMBEDDING_API_KEY=sk-xxxx
FLOW_EMBEDDING_BASE_URL=https://xxxx/v1
EMBEDDING_API_KEY=sk-xxxx
EMBEDDING_BASE_URL=https://xxxx/v1
FLOW_LLM_API_KEY=sk-xxxx
FLOW_LLM_BASE_URL=https://xxxx/v1
LLM_API_KEY=sk-xxxx
LLM_BASE_URL=https://xxxx/v1
FLOW_ES_HOSTS=http://0.0.0.0:9200
ES_HOSTS=http://0.0.0.0:9200
DASHSCOPE_API_KEY=sk-xxxx
FLOW_USE_FRAMEWORK=true

View file

@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
name = "reme_ai"
version = "0.1.0"
description = "Remember me of memory and experience"
authors = [{ name = "reme_ai team", email = "reme_ai@alibaba-inc.com" }]
authors = [{ name = "reme_ai_team", email = "reme_ai_team@alibaba-inc.com" }]
license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.12"

View file

@ -97,32 +97,36 @@ flow:
description: "Whether to update the utility attribute of the retrieved task memory."
required: true
delete_task_memory:
flow_content: delete_memory_op >> update_vector_store_op
description: "Delete task memories when utility/freq < utility_threshold and freq >= freq_threshold"
input_schema:
workspace_id:
type: "str"
description: "workspace id"
required: true
freq_threshold:
type: "int"
description: "The retrieved frequency threshold for deleting task memory."
required: true
utility_threshold:
type: "float"
description: "The utility/freq threshold for deleting task memory."
required: true
# reconsolidate_personal_memory:
# flow_content: load_not_reflected_memory_op >> get_reflection_subject_op >> update_insight_op >> long_contra_repeat_op >> update_vector_store_op
# description: "Consolidate personal memories by generating topic insights, updating values, resolving conflicts, and updating vector store"
delete_task_memory:
flow_content: delete_memory_op >> update_vector_store_op
description: "Delete task memories when utility/freq < utility_threshold and freq >= freq_threshold"
input_schema:
workspace_id:
type: "str"
description: "workspace id"
required: true
freq_threshold:
type: "int"
description: "The retrieved frequency threshold for deleting task memory."
required: true
utility_threshold:
type: "float"
description: "The utility/freq threshold for deleting task memory."
required: true
react:
flow_content: simple_react_op
description: "React to the current task with an agent"
input_schema:
query:
type: "str"
description: "user query"
required: true
llm:
default:
backend: openai_compatible
# model_name: qwen3-30b-a3b-thinking-2507
# model_name: qwen3-30b-a3b-thinking-2507
model_name: qwen3-30b-a3b-instruct-2507
params:
temperature: 0.6
@ -138,6 +142,6 @@ vector_store:
default:
backend: elasticsearch
embedding_model: default
params:
# hosts: "http://localhost:9200"
hosts: "http://11.160.132.46:8200"
# params:
# hosts: "http://localhost:9200"
# hosts: "http://11.160.132.46:8200"

View file

@ -1 +1 @@
from .simple_react_op import SimpleReactOp
from .simple_react_op import SimpleReactOp

View file

@ -1,10 +1,10 @@
from flowllm import C
from flowllm.context.flow_context import FlowContext
from flowllm.op.agent.react_v1_op import ReactV1Op
from flowllm.op.agent.react_v2_op import ReactV2Op
@C.register_op()
class SimpleReactOp(ReactV1Op):
class SimpleReactOp(ReactV2Op):
...
@ -18,4 +18,4 @@ if __name__ == "__main__":
op(context=context)
# from reme_ai.schema import Message
# result = op.llm.chat(messages=[Message(**{"role": "user", "content": "你叫什么名字?"})])
# print("!!!", result)
# print("!!!", result)

View file

@ -1,2 +1,2 @@
from . import task
from . import personal
from . import task

View file

@ -1,6 +1,6 @@
query_build: |
# Execution Process
{execution_process}
Read through the entire execution process to understand which part is currently being executed.
Generate a `query` that reflects the current state, which will later be used to search for similar problems in the database and help resolve the issue at hand.
# Execution Process
{execution_process}
Read through the entire execution process to understand which part is currently being executed.
Generate a `query` that reflects the current state, which will later be used to search for similar problems in the database and help resolve the issue at hand.

View file

@ -20,8 +20,7 @@ class MergeMemoryOp(BaseOp):
if not memory.content:
continue
content_collector.append(f"- when_to_use: {memory.when_to_use}\n"
f"content: {memory.content}\n")
content_collector.append(f"- {memory.when_to_use} {memory.content}\n")
content_collector.append("Please consider the helpful parts from these in answering the question, "
"to make the response more comprehensive and substantial.")
self.context.response.answer = "\n".join(content_collector)

View file

@ -146,4 +146,4 @@ class RerankMemoryOp(BaseLLMOp):
except Exception as e:
logger.error(f"Error parsing rerank response: {e}")
return []
return []

View file

@ -33,7 +33,7 @@ class RewriteMemoryOp(BaseLLMOp):
# Generate initial context message
rewritten_memory = self._generate_context_message(query, messages, memory_list)
# Store results in context
self.context.response.answer = rewritten_memory
self.context.response.metadata["memory_list"] = [memory.model_dump() for memory in memory_list]
@ -41,7 +41,6 @@ class RewriteMemoryOp(BaseLLMOp):
def _generate_context_message(self, query: str, messages: List[Message], memories: List[BaseMemory]) -> str:
"""Generate context message from retrieved memories"""
if not memories:
return ""
try:
@ -147,4 +146,4 @@ class RewriteMemoryOp(BaseLLMOp):
# If JSON parsing fails, return the response as-is for fallback
return response.strip()
return ""
return ""

View file

@ -1 +1 @@
from flowllm.schema.message import Message, Trajectory, ToolCall, Role
from flowllm.schema.message import Message, Role, Trajectory # noqa

View file

@ -22,8 +22,8 @@ class BaseMemory(BaseModel, ABC):
metadata: dict = Field(default_factory=dict)
def update_modified_time(self):
self.modified_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.time_modified = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def update_metadata(self, new_metadata):
self.metadata = new_metadata

View file

@ -1,2 +1,2 @@
from . import personal
from . import task
from . import personal

View file

@ -5,4 +5,4 @@ from .get_reflection_subject_op import GetReflectionSubjectOp
from .info_filter_op import InfoFilterOp
from .load_today_memory_op import LoadTodayMemoryOp
from .long_contra_repeat_op import LongContraRepeatOp
from .update_insight_op import UpdateInsightOp
from .update_insight_op import UpdateInsightOp

View file

@ -101,7 +101,7 @@ class GetReflectionSubjectOp(BaseLLMOp):
Args:
memory_contents: List of memory content strings
existing_subjects: List of already existing subject strings
user_name: Target user name
user_name: Target username
num_questions: Maximum number of new subjects to generate
Returns:

View file

@ -1,11 +1,11 @@
info_filter_system_zh: |
任务:对所给{batch_size}个句子中所含有的关于{user_name}的信息打分分数为0,1,2或3。
注意其中0表示不包含用户信息1表示句子中只包含用户假设的信息或者用户虚构的内容比如用户创作的小说或剧本2表示包含用户的一般信息时效性信息或者需要猜测才能得到的用户信息3表示明确含有或者可以确定推断出关于用户的重要信息或者用户要求记录。
{user_name}的重要信息可以包含用户基本信息,用户画像信息,用户兴趣偏好信息,用户性格,用户价值观,用户人际关系,用户重大事件转折点等等重要信息。
对每个句子都做一次信息打分,一共输出{batch_size}个分数,不需要写最终结果。
请一定要按如下格式依次输出,最后的结果一定要加<>:
思考思考的依据和过程30字以内。
结果:<句子序号> <分数:0或1或2或3>
任务:对所给{batch_size}个句子中所含有的关于{user_name}的信息打分分数为0,1,2或3。
注意其中0表示不包含用户信息1表示句子中只包含用户假设的信息或者用户虚构的内容比如用户创作的小说或剧本2表示包含用户的一般信息时效性信息或者需要猜测才能得到的用户信息3表示明确含有或者可以确定推断出关于用户的重要信息或者用户要求记录。
{user_name}的重要信息可以包含用户基本信息,用户画像信息,用户兴趣偏好信息,用户性格,用户价值观,用户人际关系,用户重大事件转折点等等重要信息。
对每个句子都做一次信息打分,一共输出{batch_size}个分数,不需要写最终结果。
请一定要按如下格式依次输出,最后的结果一定要加<>:
思考思考的依据和过程30字以内。
结果:<句子序号> <分数:0或1或2或3>
info_filter_system: |
Task: Score the information about {user_name} contained in the given batch of {batch_size} sentences, with scores of 0, 1, 2, or 3.
@ -118,7 +118,7 @@ info_filter_few_shot: |
Sentences:
1 {user_name}: I've just joined Google.
2 {user_name}: There are too many mosquitoes when sleeping outdoors. What should I do?
3 {user_name}: Is creativity related to extraversion?
3 {user_name}: Is creativity related to extroversion?
4 {user_name}: Should the review, modification, and standardization of all personnel files in a district or county be handled by the cadre archive center or by the county human resources and social security bureau?
5 {user_name}: If I am planning to have a child with a woman, as a man, how can I protect the woman and the baby and how can I prepare to ensure high sperm quality for the benefit of the offspring?
6 {user_name}: If we go out to play together, would you feel happy?

View file

@ -76,7 +76,7 @@ class UpdateInsightOp(BaseLLMOp):
logger.info(f"Successfully updated {len(updated_insights)} insight memories")
def _score_insights_by_relevance(self, insight_memories: List[PersonalMemory],
observation_memories: List[PersonalMemory],
observation_memories: List[PersonalMemory],
threshold: float) -> List[tuple]:
"""
Score insight memories based on relevance to observation memories.

View file

@ -31,7 +31,7 @@ class ComparativeExtractionOp(BaseLLMOp):
# Hard comparison: success vs failure (if similarity search is enabled)
if (success_trajectories and failure_trajectories and
self.op_params.get("enable_similarity_comparison", False)):
self.op_params.get("enable_similarity_comparison", False)):
similar_pairs = self._find_similar_step_sequences(success_trajectories, failure_trajectories)
logger.info(f"Found {len(similar_pairs)} similar pairs for hard comparison")
@ -47,7 +47,8 @@ class ComparativeExtractionOp(BaseLLMOp):
self.context.comparative_task_memories = comparative_task_memories
@staticmethod
def _find_highest_lowest_scoring_trajectories(trajectories: List[Trajectory]) -> Tuple[Optional[Trajectory], Optional[Trajectory]]:
def _find_highest_lowest_scoring_trajectories(trajectories: List[Trajectory]) -> Tuple[
Optional[Trajectory], Optional[Trajectory]]:
"""Find the highest and lowest scoring trajectories"""
if len(trajectories) < 2:
return None, None
@ -107,7 +108,8 @@ class ComparativeExtractionOp(BaseLLMOp):
return self.llm.chat(messages=[Message(content=prompt)], callback_fn=parse_task_memories)
def _extract_hard_comparative_task_memory(self, success_steps: List[Message],
failure_steps: List[Message], similarity_score: float) -> List[BaseMemory]:
failure_steps: List[Message], similarity_score: float) -> List[
BaseMemory]:
"""Extract hard comparative task memory (success vs failure)"""
prompt = self.prompt_format(
prompt_name="hard_comparative_step_task_memory_prompt",
@ -134,7 +136,6 @@ class ComparativeExtractionOp(BaseLLMOp):
return self.llm.chat(messages=[Message(content=prompt)], callback_fn=parse_task_memories)
@staticmethod
def _get_trajectory_steps(trajectory: Trajectory) -> List[Message]:
"""Get trajectory steps, prioritizing segmented steps"""
@ -148,7 +149,8 @@ class ComparativeExtractionOp(BaseLLMOp):
return trajectory.messages
def _find_similar_step_sequences(self, success_trajectories: List[Trajectory],
failure_trajectories: List[Trajectory]) -> List[Tuple[List[Message], List[Message], float]]:
failure_trajectories: List[Trajectory]) -> List[
Tuple[List[Message], List[Message], float]]:
"""Find similar step sequences for comparison"""
if not self.op_params.get("enable_similarity_comparison", False):
return []
@ -184,7 +186,8 @@ class ComparativeExtractionOp(BaseLLMOp):
failure_texts = [merge_messages_content(seq) for seq in failure_step_sequences]
# Get embedding vectors
if hasattr(self.context, 'vector_store') and self.context.vector_store and hasattr(self.context.vector_store, 'embedding_model'):
if hasattr(self.context, 'vector_store') and self.context.vector_store and hasattr(
self.context.vector_store, 'embedding_model'):
success_embeddings = self.context.vector_store.embedding_model.get_embeddings(success_texts)
failure_embeddings = self.context.vector_store.embedding_model.get_embeddings(failure_texts)

View file

@ -15,7 +15,7 @@ class FailureExtractionOp(BaseLLMOp):
def execute(self):
"""Extract task memories from failed trajectories"""
failure_trajectories: List[Trajectory] = self.context.get("failure_trajectories", [])
if not failure_trajectories:
logger.info("No failure trajectories found for extraction")
return
@ -23,7 +23,7 @@ class FailureExtractionOp(BaseLLMOp):
logger.info(f"Extracting task memories from {len(failure_trajectories)} failed trajectories")
failure_task_memories = []
# Process trajectories
for trajectory in failure_trajectories:
if hasattr(trajectory, 'segments') and trajectory.segments:

View file

@ -1,7 +1,8 @@
from typing import List
from loguru import logger
from flowllm import C, BaseOp
from loguru import logger
from reme_ai.schema.memory import BaseMemory
@ -23,8 +24,9 @@ class MemoryDeduplicationOp(BaseOp):
# Perform deduplication
deduplicated_task_memories = self._deduplicate_task_memories(task_memories)
logger.info(f"Deduplication complete: {len(deduplicated_task_memories)} deduplicated task memories out of {len(task_memories)}")
logger.info(
f"Deduplication complete: {len(deduplicated_task_memories)} deduplicated task memories out of {len(task_memories)}")
# Update context
self.context.memory_list = deduplicated_task_memories
@ -84,7 +86,8 @@ class MemoryDeduplicationOp(BaseOp):
if hasattr(node, 'embedding') and node.embedding:
existing_embeddings.append(node.embedding)
logger.debug(f"Retrieved {len(existing_embeddings)} existing task memory embeddings from workspace {workspace_id}")
logger.debug(
f"Retrieved {len(existing_embeddings)} existing task memory embeddings from workspace {workspace_id}")
return existing_embeddings
except Exception as e:
@ -111,10 +114,9 @@ class MemoryDeduplicationOp(BaseOp):
logger.error(f"Error generating embedding for task memory: {e}")
return None
def _is_similar_to_existing_task_memories(self, current_embedding: List[float],
existing_embeddings: List[List[float]],
threshold: float) -> bool:
existing_embeddings: List[List[float]],
threshold: float) -> bool:
"""Check if current embedding is similar to existing embeddings"""
for existing_embedding in existing_embeddings:
similarity = self._calculate_cosine_similarity(current_embedding, existing_embedding)
@ -124,8 +126,8 @@ class MemoryDeduplicationOp(BaseOp):
return False
def _is_similar_to_current_task_memories(self, current_embedding: List[float],
current_task_memories: List[BaseMemory],
threshold: float) -> bool:
current_task_memories: List[BaseMemory],
threshold: float) -> bool:
for existing_task_memory in current_task_memories:
existing_embedding = self._get_task_memory_embedding(existing_task_memory)
if existing_embedding is None:
@ -158,4 +160,4 @@ class MemoryDeduplicationOp(BaseOp):
except Exception as e:
logger.error(f"Error calculating cosine similarity: {e}")
return 0.0
return 0.0

View file

@ -40,7 +40,7 @@ class MemoryValidationOp(BaseLLMOp):
logger.warning(f"Task memory validation failed: {reason}")
logger.info(f"Validated {len(validated_task_memories)} out of {len(task_memories)} task memories")
# Update context
self.context.response.answer = json.dumps([x.model_dump() for x in validated_task_memories])
self.context.response.metadata["memory_list"] = validated_task_memories
@ -62,7 +62,7 @@ class MemoryValidationOp(BaseLLMOp):
def parse_validation(message: Message) -> Dict[str, Any]:
try:
response_content = message.content
# Parse validation result
# Extract JSON blocks
json_pattern = r'```json\s*([\s\S]*?)\s*```'
@ -73,19 +73,20 @@ class MemoryValidationOp(BaseLLMOp):
else:
parsed = {}
is_valid = parsed.get("is_valid",True)
score = parsed.get("score",0.5)
is_valid = parsed.get("is_valid", True)
score = parsed.get("score", 0.5)
# Set validation threshold
validation_threshold = self.op_params.get("validation_threshold", 0.5)
return {
"is_valid": is_valid and score >= validation_threshold,
"score": score,
"feedback": response_content,
"reason": "" if (is_valid and score >= validation_threshold) else f"Low validation score ({score:.2f}) or marked as invalid"
"reason": "" if (
is_valid and score >= validation_threshold) else f"Low validation score ({score:.2f}) or marked as invalid"
}
except Exception as e_inner:
logger.exception(f"Error parsing validation response: {e_inner}")
return {
@ -104,4 +105,4 @@ class MemoryValidationOp(BaseLLMOp):
"score": 0.0,
"feedback": "",
"reason": f"LLM validation error: {str(e)}"
}
}

View file

@ -12,14 +12,14 @@ class PDFPreprocessOp(BaseOp):
"""Process PDF files using MinerU and chunk content"""
pdf_path = self.context.get("pdf_path")
output_dir = self.context.get("output_dir")
if not pdf_path:
logger.error("No PDF path provided in context")
return
# Process PDF
processor = MinerUPDFProcessor(log_level="INFO")
try:
content_list, markdown_content = processor.process_pdf(
pdf_path=pdf_path,
@ -28,21 +28,21 @@ class PDFPreprocessOp(BaseOp):
lang=self.op_params.get("lang"),
backend=self.op_params.get("backend", "pipeline")
)
# Create chunks if requested
chunks = []
if self.op_params.get("create_chunks", True):
max_length = self.op_params.get("max_chunk_length", 4000)
chunks = chunk_pdf_content(content_list, max_length=max_length)
# Store results in context
self.context.pdf_content_list = content_list
self.context.pdf_markdown_content = markdown_content
self.context.pdf_chunks = chunks
logger.info(f"PDF processing completed: {len(content_list)} content blocks, "
f"{len(chunks)} chunks, {len(markdown_content)} characters of markdown")
f"{len(chunks)} chunks, {len(markdown_content)} characters of markdown")
except Exception as e:
logger.error(f"PDF processing failed: {e}")
self.context.pdf_content_list = []

View file

@ -32,9 +32,9 @@ class SimpleComparativeSummaryOp(BaseLLMOp):
task_memory_content = tm_dict.get("experience", "").strip()
if when_to_use and task_memory_content:
task_memory_list.append(TaskMemory(workspace_id=self.context.get("workspace_id", ""),
when_to_use=when_to_use,
when_to_use=when_to_use,
content=task_memory_content,
author=getattr(self.llm, 'model_name', 'system')))
author=getattr(self.llm, 'model_name', 'system')))
return task_memory_list
@ -62,7 +62,7 @@ class SimpleComparativeSummaryOp(BaseLLMOp):
if task_trajectories[0].score > task_trajectories[-1].score:
task_memories = self.compare_summary_trajectory(trajectory_a=task_trajectories[0],
trajectory_b=task_trajectories[-1])
trajectory_b=task_trajectories[-1])
memory_list.extend(task_memories)
self.context.response.answer = json.dumps([x.model_dump() for x in memory_list])

View file

@ -15,7 +15,7 @@ class SuccessExtractionOp(BaseLLMOp):
def execute(self):
"""Extract task memories from successful trajectories"""
success_trajectories: List[Trajectory] = self.context.success_trajectories
if not success_trajectories:
logger.info("No success trajectories found for extraction")
return
@ -23,7 +23,7 @@ class SuccessExtractionOp(BaseLLMOp):
logger.info(f"Extracting task memories from {len(success_trajectories)} successful trajectories")
success_task_memories = []
# Process trajectories
for trajectory in success_trajectories:
if "segments" in trajectory.metadata:

View file

@ -1,4 +1,3 @@
import json
from typing import List, Dict
from flowllm import C, BaseOp
@ -57,7 +56,7 @@ class TrajectoryPreprocessOp(BaseOp):
def _modify_tool_calls(self, messages: List[Dict]) -> List[Dict]:
new_messages = []
for msg in messages:
if 'tool_calls' in msg:
processed_tool_calls = []
@ -72,5 +71,5 @@ class TrajectoryPreprocessOp(BaseOp):
processed_tool_calls.append(tool_call)
msg['tool_calls'] = processed_tool_calls
new_messages.append(msg)
return new_messages

View file

@ -35,9 +35,8 @@ class TrajectorySegmentationOp(BaseLLMOp):
segmented_count += 1
logger.info(f"Segmented {segmented_count} trajectories")
# Update context with segmented trajectories
# Update context with segmented trajectories
def _get_target_trajectories(self, all_trajectories: List[Trajectory],
success_trajectories: List[Trajectory],
@ -116,4 +115,4 @@ class TrajectorySegmentationOp(BaseLLMOp):
numbers = re.findall(r'\b\d+\b', response)
segment_points = [int(num) for num in numbers if int(num) > 0]
return sorted(list(set(segment_points)))
return sorted(list(set(segment_points)))

View file

@ -231,7 +231,7 @@ class MinerUPDFProcessor:
Returns:
Tuple[List[Dict[str, Any]], str]: A tuple containing:
- content_list: Structured content list with metadata
- markdown_content: Raw markdown text content
- markdown_content: Raw Markdown text content
"""
# Locate generated output files - handle both flat and nested directory structures
md_file = output_dir / f"{file_stem}.md"
@ -277,7 +277,8 @@ class MinerUPDFProcessor:
item[field_name] = str(absolute_img_path)
self.logger.debug(f"Updated {field_name}: {img_path} -> {item[field_name]}")
self.logger.info(f"Successfully read JSON file: {json_file}, containing {len(content_list)} content blocks")
self.logger.info(
f"Successfully read JSON file: {json_file}, containing {len(content_list)} content blocks")
except Exception as e:
self.logger.warning(f"Failed to read JSON file {json_file}: {e}")
@ -466,32 +467,32 @@ class MinerUPDFProcessor:
"table_count": 0,
"has_formulas": False
}
for item in content_list:
if not isinstance(item, dict):
continue
content_type = item.get("type", "unknown")
stats["content_types"][content_type] = stats["content_types"].get(content_type, 0) + 1
if content_type == "text":
text = item.get("text", "")
stats["text_stats"]["total_characters"] += len(text)
stats["text_stats"]["total_words"] += len(text.split())
level = item.get("text_level", 0)
if level > 0:
stats["text_stats"]["title_levels"][level] = stats["text_stats"]["title_levels"].get(level, 0) + 1
elif content_type == "image":
stats["image_count"] += 1
elif content_type == "table":
stats["table_count"] += 1
elif content_type == "formula":
stats["has_formulas"] = True
return stats
def validate_output_quality(self, content_list: List[Dict[str, Any]], markdown_content: str) -> Dict[str, Any]:
@ -518,20 +519,20 @@ class MinerUPDFProcessor:
"suggestions": [],
"quality_score": 100
}
# Check if content was extracted
if not content_list and not markdown_content.strip():
validation["is_valid"] = False
validation["warnings"].append("No content was extracted from the PDF")
validation["quality_score"] = 0
return validation
# Check content diversity
stats = self.get_content_statistics(content_list)
if stats["total_blocks"] < 5:
validation["warnings"].append("Very few content blocks extracted - document may be complex or image-heavy")
validation["quality_score"] -= 20
# Check text content ratio
text_blocks = stats["content_types"].get("text", 0)
if text_blocks == 0:
@ -540,16 +541,17 @@ class MinerUPDFProcessor:
elif text_blocks / stats["total_blocks"] < 0.3:
validation["suggestions"].append("Low text content ratio - document may benefit from OCR processing")
validation["quality_score"] -= 10
# Check for images without processing
if stats["image_count"] > 0 and stats["content_types"].get("text", 0) == 0:
validation["suggestions"].append("Images detected but no text extracted - consider using VLM backend for image analysis")
validation["suggestions"].append(
"Images detected but no text extracted - consider using VLM backend for image analysis")
# Check markdown length vs content blocks
if len(markdown_content.strip()) < 100 and stats["total_blocks"] > 10:
validation["warnings"].append("Markdown content seems unusually short for the number of content blocks")
validation["quality_score"] -= 15
return validation
@ -656,22 +658,23 @@ if __name__ == "__main__":
and working with the extracted content.
"""
import sys
# Example usage
def example_usage():
"""Demonstrate basic PDF processing workflow."""
try:
# Initialize processor
processor = MinerUPDFProcessor.create_with_defaults(log_level="INFO")
# Example PDF path (replace with actual PDF file)
pdf_path = "example_document.pdf"
if not Path(pdf_path).exists():
print(f"Example PDF file not found: {pdf_path}")
print("Please provide a valid PDF file path to test the processor.")
return
# Process PDF with different methods
print("Processing PDF with auto method...")
content_list, markdown_content = processor.process_pdf(
@ -679,7 +682,7 @@ if __name__ == "__main__":
method="auto",
lang="en" # Specify language for better OCR results
)
# Generate statistics
stats = processor.get_content_statistics(content_list)
print(f"Processing Statistics:")
@ -687,7 +690,7 @@ if __name__ == "__main__":
print(f" Content types: {stats['content_types']}")
print(f" Text characters: {stats['text_stats']['total_characters']}")
print(f" Text words: {stats['text_stats']['total_words']}")
# Validate output quality
validation = processor.validate_output_quality(content_list, markdown_content)
print(f"Quality Score: {validation['quality_score']}/100")
@ -695,7 +698,7 @@ if __name__ == "__main__":
print("Warnings:", validation['warnings'])
if validation['suggestions']:
print("Suggestions:", validation['suggestions'])
# Save results
output_path = Path(pdf_path).stem + "_processed"
saved_files = processor.save_results(
@ -704,19 +707,20 @@ if __name__ == "__main__":
output_path=output_path
)
print(f"Results saved to: {saved_files}")
# Create text chunks for downstream processing
chunks = chunk_pdf_content(content_list, max_length=2000)
print(f"Created {len(chunks)} text chunks")
# Display first chunk as example
if chunks:
print("First chunk preview:")
print(chunks[0][:200] + "..." if len(chunks[0]) > 200 else chunks[0])
except Exception as e:
print(f"Error during processing: {e}")
sys.exit(1)
# Run example if script is executed directly
example_usage()

View file

@ -10,6 +10,7 @@ from loguru import logger
def merge_messages_content(messages: List[Message | dict]) -> str:
return merge_messages_content_flowllm(messages)
def parse_json_experience_response(response: str) -> List[dict]:
"""Parse JSON formatted experience response"""
try:
@ -82,155 +83,6 @@ def get_trajectory_context(trajectory: Trajectory, step_sequence: List[Message])
logger.error(f"Error getting trajectory context: {e}")
return f"Query: {trajectory.metadata.get('query', 'N/A')}"
def parse_observation_response(response_text: str) -> List[dict]:
"""Parse observation response to extract structured data"""
# Pattern to match both Chinese and English observation formats
pattern = r"信息:<(\d+)>\s*<>\s*<([^<>]+)>\s*<([^<>]*)>|Information:\s*<(\d+)>\s*<>\s*<([^<>]+)>\s*<([^<>]*)>"
matches = re.findall(pattern, response_text, re.IGNORECASE | re.MULTILINE)
observations = []
for match in matches:
# Handle both Chinese and English patterns
if match[0]: # Chinese pattern
idx_str, content, keywords = match[0], match[1], match[2]
else: # English pattern
idx_str, content, keywords = match[3], match[4], match[5]
try:
idx = int(idx_str)
# Skip if content indicates no meaningful observation
content_lower = content.lower().strip()
if content_lower not in ['', 'none', '', 'repeat']:
observations.append({
"index": idx,
"content": content.strip(),
"keywords": keywords.strip() if keywords else ""
})
except ValueError:
logger.warning(f"Invalid index format: {idx_str}")
continue
return observations
def parse_observation_with_time_response(response_text: str) -> List[dict]:
"""Parse observation with time response to extract structured data"""
# Pattern to match both Chinese and English observation formats with time information
# Chinese: 信息:<1> <时间信息或不输出> <明确的重要信息或"无"> <关键词>
# English: Information: <1> <Time information or do not output> <Clear important information or "None"> <Keywords>
pattern = r"信息:<(\d+)>\s*<([^<>]*)>\s*<([^<>]+)>\s*<([^<>]*)>|Information:\s*<(\d+)>\s*<([^<>]*)>\s*<([^<>]+)>\s*<([^<>]*)>"
matches = re.findall(pattern, response_text, re.IGNORECASE | re.MULTILINE)
observations = []
for match in matches:
# Handle both Chinese and English patterns
if match[0]: # Chinese pattern
idx_str, time_info, content, keywords = match[0], match[1], match[2], match[3]
else: # English pattern
idx_str, time_info, content, keywords = match[4], match[5], match[6], match[7]
try:
idx = int(idx_str)
# Skip if content indicates no meaningful observation
content_lower = content.lower().strip()
if content_lower not in ['', 'none', '', 'repeat']:
observations.append({
"index": idx,
"time_info": time_info.strip() if time_info else "",
"content": content.strip(),
"keywords": keywords.strip() if keywords else ""
})
except ValueError:
logger.warning(f"Invalid index format: {idx_str}")
continue
return observations
def parse_reflection_subjects_response(response_text: str, existing_subjects: List[str] = None) -> List[str]:
"""Parse reflection subjects response to extract new subject attributes"""
if existing_subjects is None:
existing_subjects = []
# Split response into lines and clean up
lines = response_text.strip().split('\n')
subjects = []
for line in lines:
line = line.strip()
# Skip empty lines, "None" responses, and existing subjects
if (line and
line not in ['', 'None', ''] and
line not in existing_subjects and
not line.startswith('新增') and # Skip Chinese header
not line.startswith('New ') and # Skip English header
len(line) > 1): # Skip single character responses
subjects.append(line)
logger.info(f"Parsed {len(subjects)} new reflection subjects from response")
return subjects
def parse_info_filter_response(response_text: str) -> List[tuple]:
"""Parse info filter response to extract message scores"""
import re
# Pattern to match both Chinese and English result formats
# Chinese: 结果:<序号> <分数>
# English: Result: <Index> <Score>
pattern = r"结果:<(\d+)>\s*<([0-3])>|Result:\s*<(\d+)>\s*<([0-3])>"
matches = re.findall(pattern, response_text, re.IGNORECASE | re.MULTILINE)
scores = []
for match in matches:
# Handle both Chinese and English patterns
if match[0]: # Chinese pattern
idx_str, score_str = match[0], match[1]
else: # English pattern
idx_str, score_str = match[2], match[3]
try:
idx = int(idx_str)
score = score_str
scores.append((idx, score))
except ValueError:
logger.warning(f"Invalid index or score format: {idx_str}, {score_str}")
continue
logger.info(f"Parsed {len(scores)} info filter scores from response")
return scores
def parse_long_contra_repeat_response(response_text: str) -> List[tuple]:
"""Parse long contra repeat response to extract judgments"""
import re
# Pattern to match both Chinese and English judgment formats
# Chinese: 判断:<序号> <矛盾|被包含|无> <修改后的内容>
# English: Judgment: <Index> <Contradiction|Contained|None> <Modified content>
pattern = r"判断:<(\d+)>\s*<(矛盾|被包含|无)>\s*<([^<>]*)>|Judgment:\s*<(\d+)>\s*<(Contradiction|Contained|None)>\s*<([^<>]*)>"
matches = re.findall(pattern, response_text, re.IGNORECASE | re.MULTILINE)
judgments = []
for match in matches:
# Handle both Chinese and English patterns
if match[0]: # Chinese pattern
idx_str, judgment, modified_content = match[0], match[1], match[2]
else: # English pattern
idx_str, judgment, modified_content = match[3], match[4], match[5]
try:
idx = int(idx_str)
judgments.append((idx, judgment, modified_content))
except ValueError:
logger.warning(f"Invalid index format: {idx_str}")
continue
logger.info(f"Parsed {len(judgments)} long contra repeat judgments from response")
return judgments
def parse_update_insight_response(response_text: str, language: str = "en") -> str:
"""Parse update insight response to extract updated insight content"""
import re
@ -261,61 +113,3 @@ def parse_update_insight_response(response_text: str, language: str = "en") -> s
logger.warning("No insight content found in response")
return ""
def load_memories_from_vector_store(workspace_id: str, filter_criteria: dict, top_k: int,
memory_category: str = ""):
"""
Load memories from vector store based on filter criteria.
Args:
workspace_id: The workspace identifier
filter_criteria: Dictionary containing filter criteria for memory retrieval
top_k: Maximum number of memories to retrieve
memory_category: Category label to add to memory metadata
Returns:
List of PersonalMemory objects loaded from vector store
"""
from reme_ai.schema.memory import PersonalMemory
try:
# This is a placeholder implementation - in a real scenario, you would
# integrate with your actual vector store (e.g., Chroma, Pinecone, etc.)
logger.info(f"Loading memories from vector store for workspace: {workspace_id}")
logger.info(f"Filter criteria: {filter_criteria}")
logger.info(f"Top K: {top_k}")
# For now, return empty list as placeholder
# In real implementation, this would:
# 1. Connect to vector store
# 2. Apply filter criteria
# 3. Retrieve top_k memories
# 4. Convert vector nodes to PersonalMemory objects
# 5. Add memory_category to metadata
memories = []
# Placeholder: Create some example memories for testing
if memory_category == "insight":
for i in range(min(top_k, 2)):
memory = PersonalMemory(
workspace_id=workspace_id,
memory_type="personal_insight",
content=f"Sample insight memory {i + 1} for {filter_criteria.get('target', 'user')}",
target=filter_criteria.get('target', 'user'),
when_to_use=f"When analyzing user behavior patterns {i + 1}",
author="system",
metadata={
"memory_category": memory_category,
"filter_criteria": filter_criteria
}
)
memories.append(memory)
logger.info(f"Loaded {len(memories)} memories from vector store")
return memories
except Exception as e:
logger.error(f"Error loading memories from vector store: {e}")
return []

View file

@ -1,9 +1,7 @@
from typing import List, Iterable
from typing import Iterable
from flowllm import C, BaseLLMOp
from flowllm.schema.vector_node import VectorNode
from loguru import logger
from reme_ai.schema.memory import BaseMemory, dict_to_memory
@C.register_op()
@ -14,14 +12,14 @@ class DeleteMemoryOp(BaseLLMOp):
workspace_id: str = self.context.workspace_id
freq_threshold: int = self.context.freq_threshold
utility_threshold: float = self.context.utility_threshold
nodes: Iterable[VectorNode] = self.vector_store._iter_workspace_nodes(workspace_id=workspace_id)
nodes: Iterable[VectorNode] = self.vector_store.iter_workspace_nodes(workspace_id=workspace_id)
deleted_memory_ids = []
for node in nodes:
freq = node["metadata"]["metadata"]["freq"]
utility = node["metadata"]["metadata"]["utility"]
if freq >= freq_threshold:
if utility*1.0 / freq < utility_threshold:
if utility * 1.0 / freq < utility_threshold:
deleted_memory_ids.append(node["unique_id"])
self.context.deleted_memory_ids = deleted_memory_ids

View file

@ -1,5 +1,6 @@
from typing import List
from flowllm import C, BaseLLMOp, BaseOp
from flowllm import C, BaseOp
from loguru import logger
from reme_ai.schema.memory import BaseMemory, dict_to_memory
@ -11,11 +12,11 @@ class UpdateMemoryFreqOp(BaseOp):
def execute(self):
memory_dicts: List[dict] = self.context.memory_dicts
if not memory_dicts:
logger.info("No memories to update freq")
return
memory_list: List[BaseMemory] = [dict_to_memory(memory_dict) for memory_dict in memory_dicts]
new_memory_list = []
deleted_memory_ids = []
@ -24,10 +25,9 @@ class UpdateMemoryFreqOp(BaseOp):
metadata = memory.metadata
metadata["freq"] = metadata.get("freq", 0) + 1
memory.update_metadata(metadata)
deleted_memory_ids.append(memory.memory_id)
new_memory_list.append(memory)
self.context.deleted_memory_ids = deleted_memory_ids
self.context.memory_list = new_memory_list

View file

@ -1,5 +1,6 @@
from typing import List
from flowllm import C, BaseLLMOp, BaseOp
from flowllm import C, BaseOp
from loguru import logger
from reme_ai.schema.memory import BaseMemory
@ -12,7 +13,7 @@ class UpdateMemoryUtilityOp(BaseOp):
def execute(self):
memory_dicts: List[dict] = self.context.memory_dicts
update_utility = self.context.update_utility
if not memory_dicts or not update_utility:
logger.info("No memories to update utility")
return
@ -24,9 +25,8 @@ class UpdateMemoryUtilityOp(BaseOp):
metadata = memory.metadata
metadata["utility"] = metadata.get("utility", 0) + 1
memory.update_metadata(metadata)
new_memory_list.append(memory)
self.context.response.metadata["memory_list"] = new_memory_list
self.context.response.metadata["deleted_memory_ids"] = self.context.deleted_memory_ids

View file

@ -17,7 +17,8 @@ class VectorStoreActionOp(BaseLLMOp):
dest_workspace_id=workspace_id)
elif action == "delete":
result = self.vector_store.delete_workspace(workspace_id=workspace_id)
if self.vector_store.exist_workspace(workspace_id):
result = self.vector_store.delete_workspace(workspace_id=workspace_id)
elif action == "delete_ids":
memory_ids: list = self.context.memory_ids
@ -25,6 +26,7 @@ class VectorStoreActionOp(BaseLLMOp):
elif action == "dump":
path: str = self.context.path
def node_to_memory(node: VectorNode) -> dict:
return vector_node_to_memory(node).model_dump()
@ -34,6 +36,7 @@ class VectorStoreActionOp(BaseLLMOp):
elif action == "load":
path: str = self.context.path
def memory_dict_to_node(memory_dict: dict) -> VectorNode:
memory: BaseMemory = dict_to_memory(memory_dict=memory_dict)
return memory.to_vector_node()
@ -41,7 +44,7 @@ class VectorStoreActionOp(BaseLLMOp):
result = self.vector_store.load_workspace(workspace_id=workspace_id,
path=path,
callback_fn=memory_dict_to_node)
else:
raise ValueError(f"invalid action={action}")

3
test_workspace.jsonl Normal file
View file

@ -0,0 +1,3 @@
{"workspace_id": "test_workspace", "memory_id": "4ff6a7a783a84940a046e557de7542f0", "memory_type": "task", "when_to_use": "When analyzing a complex organization with multiple dimensions (history, products, finances, leadership), especially when the query is open-ended or broad like 'analyze [company]'", "content": "The agent successfully decomposed the task into four targeted web searches: company overview/history, products/services, financial performance, and leadership impact. This multi-angle approach ensured comprehensive coverage of key domains. Each search was designed to extract distinct, high-value information layers, enabling synthesis into a structured, well-rounded analysis. The use of varied queries prevented redundancy and maximized information diversity.", "score": 0.0, "time_created": "2025-08-31 15:42:44", "time_modified": "2025-08-31 15:42:44", "author": "qwen3-30b-a3b-instruct-2507", "metadata": {"when_to_use": "When analyzing a complex organization with multiple dimensions (history, products, finances, leadership), especially when the query is open-ended or broad like 'analyze [company]'", "experience": "The agent successfully decomposed the task into four targeted web searches: company overview/history, products/services, financial performance, and leadership impact. This multi-angle approach ensured comprehensive coverage of key domains. Each search was designed to extract distinct, high-value information layers, enabling synthesis into a structured, well-rounded analysis. The use of varied queries prevented redundancy and maximized information diversity.", "tags": ["multi-faceted analysis", "information decomposition", "web_search strategy", "comprehensive coverage"], "confidence": 0.9, "step_type": "reasoning", "tools_used": ["web_search"]}}
{"workspace_id": "test_workspace", "memory_id": "2ad5b6aae8724e87944e69ea4476a97c", "memory_type": "task", "when_to_use": "When initial results from a general search are insufficient or too broad, and deeper insights are needed on specific subtopics such as financial trends, product portfolios, or leadership dynamics", "content": "The agent proactively called the same tool (web_search) multiple times with different, focused queries to gather information from various perspectives. This iterative technique allowed for layered understanding—e.g., separating historical milestones from current financial challenges and leadership influence. It demonstrated strategic use of repetition not as redundancy but as a method to triangulate data across domains, significantly improving depth and accuracy.", "score": 0.0, "time_created": "2025-08-31 15:42:44", "time_modified": "2025-08-31 15:42:44", "author": "qwen3-30b-a3b-instruct-2507", "metadata": {"when_to_use": "When initial results from a general search are insufficient or too broad, and deeper insights are needed on specific subtopics such as financial trends, product portfolios, or leadership dynamics", "experience": "The agent proactively called the same tool (web_search) multiple times with different, focused queries to gather information from various perspectives. This iterative technique allowed for layered understanding—e.g., separating historical milestones from current financial challenges and leadership influence. It demonstrated strategic use of repetition not as redundancy but as a method to triangulate data across domains, significantly improving depth and accuracy.", "tags": ["iterative querying", "perspective diversification", "information triangulation", "search optimization"], "confidence": 0.85, "step_type": "action", "tools_used": ["web_search"]}}
{"workspace_id": "test_workspace", "memory_id": "87afc1367c6c4b739bbe191e1f47c875", "memory_type": "task", "when_to_use": "After gathering raw data from external sources, when synthesizing findings into a coherent, structured response that balances factual reporting with critical insight", "content": "The agent effectively integrated results from multiple searches into a unified summary with clear sectioning: history, products, finance, and leadership. It highlighted both strengths (innovation, mission-driven vision) and risks (financial decline, leadership dependency). This synthesis transformed fragmented data into actionable intelligence, demonstrating how structured summarization enhances usability and decision-making value in agent outputs.", "score": 0.0, "time_created": "2025-08-31 15:42:44", "time_modified": "2025-08-31 15:42:44", "author": "qwen3-30b-a3b-instruct-2507", "metadata": {"when_to_use": "After gathering raw data from external sources, when synthesizing findings into a coherent, structured response that balances factual reporting with critical insight", "experience": "The agent effectively integrated results from multiple searches into a unified summary with clear sectioning: history, products, finance, and leadership. It highlighted both strengths (innovation, mission-driven vision) and risks (financial decline, leadership dependency). This synthesis transformed fragmented data into actionable intelligence, demonstrating how structured summarization enhances usability and decision-making value in agent outputs.", "tags": ["synthesis", "structured summarization", "critical insight", "data integration"], "confidence": 0.9, "step_type": "observation", "tools_used": []}}