Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ tRPC-Agent-Python provides an end-to-end foundation for agent building, orchestr

- **Multi-paradigm agent orchestration**: Built-in orchestration supports `ChainAgent` / `ParallelAgent` / `CycleAgent` / `TransferAgent`, with `GraphAgent` for graph-based orchestration.
- **Graph orchestration capability (`GraphAgent`)**: Use DSL to orchestrate `Agent` / `Tool` / `MCP` / `Knowledge` / `CodeExecutor` in one unified flow.
- **Efficient integration with Python AI ecosystems**: Agent ecosystem extensions (`claude-agent-sdk` / `LangGraph`, etc.) / Tool ecosystem extensions (`mcp`, etc.) / Knowledge ecosystem extensions (`LangChain`, etc.) / Model ecosystem extensions (`LiteLLM`, etc.) / Memory ecosystem extensions (`Mem0`, etc.).
- **Efficient integration with Python AI ecosystems**: Agent ecosystem extensions (`claude-agent-sdk` / `LangGraph`, etc.) / Tool ecosystem extensions (`mcp`, etc.) / Knowledge ecosystem extensions (`LangChain`, etc.) / Model ecosystem extensions (`LiteLLM`, etc.) / Memory ecosystem extensions (`Mem0`, `Mempalace`, etc.).
- **Agent ecosystem extensions**: Supports `LangGraphAgent` / `ClaudeAgent` / `TeamAgent` (Agno-Like).
- **Tool ecosystem extensions**: `FunctionTool` / File tools / `MCPToolset` / LangChain Tool / Agent-as-Tool.
- **Complete memory capability (`Session` / `Memory`)**: `Session` manages messages and state within a single session, while `Memory` manages cross-session long-term memory and personalization. Persistence supports `InMemory` / `Redis` / `SQL`; `Memory` also supports `Mem0`.
- **Complete memory capability (`Session` / `Memory`)**: `Session` manages messages and state within a single session, while `Memory` manages cross-session long-term memory and personalization. Persistence supports `InMemory` / `Redis` / `SQL`; `Memory` also supports `Mem0`、`Mempalace`.
- **Production-grade knowledge capability**: Built on LangChain components with first-class RAG support.
- **CodeExecutor extension capability**: Supports local / container executors for code execution and task grounding.
- **Skills extension capability**: Supports `SKILL.md`-based skill systems for reusable capabilities and dynamic tooling.
Expand Down Expand Up @@ -84,7 +84,7 @@ pip install trpc-agent-py
Install optional capabilities as needed:

```bash
pip install trpc-agent-py[a2a,ag-ui,knowledge,agent-claude,mem0,langfuse]
pip install trpc-agent-py[a2a,ag-ui,knowledge,agent-claude,mem0, Mempalace, langfuse]
```

### Develop Weather Agent
Expand Down Expand Up @@ -457,7 +457,7 @@ Related docs:
This group helps you:

- Session: manage per-session messages, summaries, and state
- Memory: manage cross-session long-term memory (including Mem0)
- Memory: manage cross-session long-term memory (including Mem0, Mempalace)
- Knowledge: cover document loading, retrieval, RAG, and prompt templates

### 10. Serving and Protocols
Expand Down Expand Up @@ -545,12 +545,12 @@ The framework is organized in an event-driven architecture where each layer can
- **Runner layer**: Unified execution entry, coordinating Session / Memory / Artifact services
- **Tool layer**: FunctionTool / file tools / MCPToolset / Skill tools
- **Model layer**: OpenAIModel / AnthropicModel / LiteLLMModel
- **Memory layer**: SessionService / MemoryService / SessionSummarizer / Mem0MemoryService
- **Memory layer**: SessionService / MemoryService / SessionSummarizer / Mem0MemoryService / MempalaceMemoryService
- **Knowledge layer**: Production-grade LangChain-based knowledge and RAG capability
- **Execution and skill layer**: CodeExecutor (local / container) / Skills
- **Service layer**: FastAPI / A2A / AG-UI
- **Observability layer**: OpenTelemetry tracing/metrics, integrable with platforms like Langfuse
- **Ecosystem adapter layer**: claude-agent-sdk / mcp / LangChain / LiteLLM / Mem0 plugged into the main chain through model/tool/memory adapters
- **Ecosystem adapter layer**: claude-agent-sdk / mcp / LangChain / LiteLLM / Mem0 / Mempalace plugged into the main chain through model/tool/memory adapters

Key packages:

Expand Down
12 changes: 6 additions & 6 deletions README.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ tRPC-Agent-Python 提供从 Agent 构建、编排、工具接入、会话记忆

- **多范式 Agent 编排**:预设编排支持 ChainAgent / ParallelAgent / CycleAgent / TransferAgent,同时支持 GraphAgent 图编排
- **图编排能力(GraphAgent)**:通过 DSL 统一编排 Agent / Tool / MCP / Knowledge / CodeExecutor
- **高效接入 Python AI 生态扩展**:Agent 生态扩展(claude-agent-sdk / LangGraph 等)/ 工具生态扩展(mcp 等)/ 知识库生态扩展(LangChain 等)/ 模型生态扩展(LiteLLM 等)/ 记忆生态扩展(Mem0
- **高效接入 Python AI 生态扩展**:Agent 生态扩展(claude-agent-sdk / LangGraph 等)/ 工具生态扩展(mcp 等)/ 知识库生态扩展(LangChain 等)/ 模型生态扩展(LiteLLM 等)/ 记忆生态扩展(Mem0、Mempalace等
- **Agent 生态扩展**:支持 LangGraphAgent / ClaudeAgent / TeamAgent(Agno-Like)
- **Tool 生态扩展**:FunctionTool / 文件工具 / MCPToolset / LangChain Tool / Agent-as-Tool
- **完善的记忆能力(Session / Memory)**:Session 负责单会话内的消息与状态管理,Memory 负责跨会话长期记忆与个性化信息沉淀。持久化支持 InMemory / Redis / SQL,Memory 还支持 Mem0
- **完善的记忆能力(Session / Memory)**:Session 负责单会话内的消息与状态管理,Memory 负责跨会话长期记忆与个性化信息沉淀。持久化支持 InMemory / Redis / SQL,Memory 还支持 Mem0、Mempalace
- **生产级知识库能力**:知识库能力基于 LangChain 组件构建,支持 RAG 场景
- **CodeExecutor 扩展能力**:支持本地 / 容器执行器,用于支持 Agent 的代码执行与任务落地能力
- **Skills 扩展能力**:支持 SKILL.md 技能体系,用于支持 Agent 的技能复用与动态工具化能力
Expand Down Expand Up @@ -84,7 +84,7 @@ pip install trpc-agent-py
按需安装扩展能力:

```bash
pip install trpc-agent-py[a2a,ag-ui,knowledge,agent-claude,mem0,langfuse]
pip install trpc-agent-py[a2a,ag-ui,knowledge,agent-claude,mem0, Mempalace, langfuse]
```


Expand Down Expand Up @@ -458,7 +458,7 @@ skill_tool_set = SkillToolSet(repository=repository, run_tool_kwargs=tool_kwargs
这组示例可以帮你:

- Session:管理单会话的消息、摘要与状态
- Memory:管理跨会话长期记忆(含 Mem0)
- Memory:管理跨会话长期记忆(含 Mem0, Mempalace
- Knowledge:覆盖文档加载、检索、RAG、提示模板等链路

### 10. 服务化与协议
Expand Down Expand Up @@ -546,12 +546,12 @@ skill_tool_set = SkillToolSet(repository=repository, run_tool_kwargs=tool_kwargs
- **Runner 层**:统一执行入口,负责 Session/Memory/Artifact 等服务协同
- **Tool 层**:FunctionTool / 文件工具 / MCPToolset / Skill 工具
- **Model 层**:OpenAIModel / AnthropicModel / LiteLLMModel
- **Memory 层**:SessionService / MemoryService / SessionSummarizer / Mem0MemoryService
- **Memory 层**:SessionService / MemoryService / SessionSummarizer / Mem0MemoryService / MempalaceMemoryService
- **Knowledge 层**:基于 LangChain 的生产级知识库能力(RAG)
- **执行与技能层**:CodeExecutor(本地/容器)/ Skills
- **服务层**:FastAPI / A2A / AG-UI
- **观测层**:OpenTelemetry tracing/metrics,可对接 Langfuse 等平台
- **生态适配层**:claude-agent-sdk / mcp / LangChain / LiteLLM / Mem0,通过模型/工具/记忆适配器接入主链路
- **生态适配层**:claude-agent-sdk / mcp / LangChain / LiteLLM / Mem0 / MemoryService,通过模型/工具/记忆适配器接入主链路

关键包一览:

Expand Down
8 changes: 8 additions & 0 deletions examples/memory_service_with_sql/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from trpc_agent_sdk.models import OpenAIModel
from trpc_agent_sdk.tools import FunctionTool
from trpc_agent_sdk.tools import load_memory_tool
from trpc_agent_sdk.types import GenerateContentConfig
from trpc_agent_sdk.types import HttpOptions

from .config import get_model_config
from .prompts import INSTRUCTION
Expand All @@ -25,12 +27,18 @@ def _create_model() -> LLMModel:

def create_agent() -> LlmAgent:
""" Create an agent"""
generate_content_config = GenerateContentConfig(
http_options=HttpOptions(extra_body={"chat_template_kwargs": {
"enable_thinking": False
}}),
)
agent = LlmAgent(
name="assistant",
description="A helpful assistant for conversation",
model=_create_model(), # You can change this to your preferred model
instruction=INSTRUCTION,
tools=[FunctionTool(get_weather_report), load_memory_tool],
generate_content_config=generate_content_config,
)
return agent

Expand Down
8 changes: 8 additions & 0 deletions examples/session_service_with_sql/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from trpc_agent_sdk.models import LLMModel
from trpc_agent_sdk.models import OpenAIModel
from trpc_agent_sdk.tools import FunctionTool
from trpc_agent_sdk.types import GenerateContentConfig
from trpc_agent_sdk.types import HttpOptions

from .config import get_model_config
from .prompts import INSTRUCTION
Expand All @@ -24,12 +26,18 @@ def _create_model() -> LLMModel:

def create_agent() -> LlmAgent:
""" Create an agent"""
generate_content_config = GenerateContentConfig(
http_options=HttpOptions(extra_body={"chat_template_kwargs": {
"enable_thinking": False
}}),
)
agent = LlmAgent(
name="assistant",
description="A helpful assistant for conversation",
model=_create_model(), # You can change this to your preferred model
instruction=INSTRUCTION,
tools=[FunctionTool(get_weather_report)],
generate_content_config=generate_content_config,
)
return agent

Expand Down
3 changes: 1 addition & 2 deletions examples/session_service_with_sql/run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,9 @@ async def run_weather_agent():
]

for query in demo_queries:
# Use a new session for each query

user_content = Content(parts=[Part.from_text(text=query)])

print(f"👤 User: {query}")
print("🤖 Assistant: ", end="", flush=True)
async for event in runner.run_async(user_id=user_id, session_id=current_session_id, new_message=user_content):
# Check if event.content exists
Expand Down
28 changes: 14 additions & 14 deletions tests/common/test_compatible.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

Covers:
- PY_310 version flag
- checkenum() with standard enums, IntEnum, and fallback path
- check_enum() with standard enums, IntEnum, and fallback path
- OSDetector: platform detection, properties, get_os_name, get_os_info, __str__
- OS_DETECTOR module-level singleton
"""
Expand All @@ -21,7 +21,7 @@

import pytest

from trpc_agent_sdk.common._compatible import OS_DETECTOR, OSDetector, PY_310, checkenum
from trpc_agent_sdk.common._compatible import OS_DETECTOR, OSDetector, PY_310, check_enum


# ---------------------------------------------------------------------------
Expand All @@ -40,7 +40,7 @@ def test_py310_matches_runtime(self):


# ---------------------------------------------------------------------------
# checkenum
# check_enum
# ---------------------------------------------------------------------------


Expand All @@ -63,32 +63,32 @@ class _FlagEnum(enum.Flag):


class TestCheckenum:
"""Tests for checkenum()."""
"""Tests for check_enum()."""

def test_valid_enum_member(self):
assert checkenum(_Color.RED, _Color) is True
assert check_enum(_Color.RED, _Color) is True

def test_invalid_enum_member(self):
assert checkenum("yellow", _Color) is False
assert check_enum("yellow", _Color) is False

def test_valid_int_enum_member(self):
assert checkenum(_Priority.HIGH, _Priority) is True
assert check_enum(_Priority.HIGH, _Priority) is True

def test_invalid_int_enum_member(self):
assert checkenum(99, _Priority) is False
assert check_enum(99, _Priority) is False

def test_valid_flag_enum_member(self):
assert checkenum(_FlagEnum.READ, _FlagEnum) is True
assert check_enum(_FlagEnum.READ, _FlagEnum) is True

def test_string_value_is_found_by_value(self):
# Python 3.12+ enum __contains__ matches by value
assert checkenum("red", _Color) is True
assert check_enum("red", _Color) is True

def test_string_not_matching_any_value(self):
assert checkenum("magenta", _Color) is False
assert check_enum("magenta", _Color) is False

def test_none_is_not_member(self):
assert checkenum(None, _Color) is False
assert check_enum(None, _Color) is False

def test_fallback_to_members_values(self):
"""When ``in`` raises, falls back to __members__.values()."""
Expand All @@ -108,8 +108,8 @@ def __contains__(self, item):
def __iter__(self):
raise TypeError("broken __iter__")

assert checkenum("a", _BadContains()) is True
assert checkenum("c", _BadContains()) is False
assert check_enum("a", _BadContains()) is True
assert check_enum("c", _BadContains()) is False


# ---------------------------------------------------------------------------
Expand Down
8 changes: 4 additions & 4 deletions tests/common/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@
from __future__ import annotations

import trpc_agent_sdk.common as common_mod
from trpc_agent_sdk.common import OS_DETECTOR, OSDetector, checkenum
from trpc_agent_sdk.common import OS_DETECTOR, OSDetector, check_enum
from trpc_agent_sdk.common._compatible import (
OS_DETECTOR as _ORIG_OS_DETECTOR,
OSDetector as _OrigOSDetector,
checkenum as _orig_checkenum,
check_enum as _orig_check_enum,
)


class TestPublicExports:
"""Ensure __init__.py re-exports the right objects."""

def test_all_contains_expected_names(self):
assert set(common_mod.__all__) == {"OSDetector", "OS_DETECTOR", "checkenum"}
assert set(common_mod.__all__) == {"OSDetector", "OS_DETECTOR", "check_enum"}

def test_os_detector_class_is_same_object(self):
assert OSDetector is _OrigOSDetector
Expand All @@ -32,4 +32,4 @@ def test_os_detector_instance_is_same_object(self):
assert OS_DETECTOR is _ORIG_OS_DETECTOR

def test_checkenum_is_same_function(self):
assert checkenum is _orig_checkenum
assert check_enum is _orig_check_enum
11 changes: 0 additions & 11 deletions tests/memory/test_sql_memory_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,17 +278,6 @@ async def test_store_skips_events_without_content(self):
svc._sql_storage.add.assert_not_called()
svc._sql_storage.commit.assert_not_called()

async def test_store_raises_on_non_session(self):
svc = SqlMemoryService.__new__(SqlMemoryService)
svc._memory_service_config = _make_config_no_ttl()
svc._sql_storage = _patch_sql_storage()
svc._SqlMemoryService__cleanup_task = None
svc._SqlMemoryService__cleanup_stop_event = None

with pytest.raises(TypeError, match="Content must be a Session"):
await svc.store_session("not a session")


# ---------------------------------------------------------------------------
# SqlMemoryService — search_memory
# ---------------------------------------------------------------------------
Expand Down
9 changes: 0 additions & 9 deletions tests/models/test_anthropic_model_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,14 +519,5 @@ def test_part_with_inline_data_is_valid(self):
request = LlmRequest(contents=[Content(parts=[part], role="user")])
model.validate_request(request)

def test_part_with_no_content_raises(self):
"""Part with no meaningful content raises ValueError."""
model = _model()
part = Part()
request = LlmRequest(contents=[Content(parts=[part], role="user")])
with pytest.raises(ValueError, match="Content parts must have"):
model.validate_request(request)


if __name__ == "__main__":
pytest.main([__file__, "-v"])
12 changes: 0 additions & 12 deletions tests/models/test_openai_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,6 @@ def test_validate_request_with_multiple_contents(self):
# Should not raise
model.validate_request(request)

def test_validate_request_with_parts_without_content(self):
"""Test validating request with parts that have no actual content raises ValueError."""
model = OpenAIModel(model_name="gpt-4", api_key="test_key")

# Create a part with all None fields
part = Part()
content = Content(parts=[part], role="user")
request = LlmRequest(contents=[content], config=None, tools_dict={})

with pytest.raises(ValueError, match="Content parts must have"):
model.validate_request(request)

def test_properties_and_config(self):
"""Test model properties and config."""
model = OpenAIModel(
Expand Down
27 changes: 27 additions & 0 deletions tests/sessions/test_sql_session_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,39 @@ def test_from_event_with_function_call(self):
storage_event = SessionStorageEvent.from_event(session, event)
assert storage_event.content is not None

def test_from_event_drops_empty_parts(self):
session = Session(id="s1", app_name="app", user_id="user", save_key="k")
event = Event(
invocation_id="inv-1",
author="agent",
content=Content(parts=[Part()]),
)
storage_event = SessionStorageEvent.from_event(session, event)
assert storage_event.content is None

def test_from_event_no_content(self):
session = Session(id="s1", app_name="app", user_id="user", save_key="k")
event = Event(invocation_id="inv-1", author="agent", actions=EventActions())
storage_event = SessionStorageEvent.from_event(session, event)
assert storage_event.content is None

def test_to_event_drops_legacy_empty_parts(self):
storage_event = SessionStorageEvent(
id="e1",
app_name="app",
user_id="user",
session_id="s1",
invocation_id="inv-1",
author="agent",
actions=EventActions(),
long_running_tool_ids=set(),
timestamp=datetime.now(),
model_flags=1,
content={"parts": [{}], "role": "model"},
)
event = storage_event.to_event()
assert event.content is None

def test_long_running_tool_ids_property(self):
session = Session(id="s1", app_name="app", user_id="user", save_key="k")
event = _make_event()
Expand Down
16 changes: 16 additions & 0 deletions tests/storage/test_sql_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,14 @@ def test_process_bind_param_spanner(self):
result = dpt.process_bind_param(value, dialect)
assert pickle.loads(result) == value

def test_process_bind_param_mysql(self):
dpt = DynamicPickleType()
dialect = _make_dialect("mysql")
value = {"key": "value", "nums": [1, 2, 3]}
result = dpt.process_bind_param(value, dialect)
assert isinstance(result, bytes)
assert pickle.loads(result) == value

def test_process_bind_param_non_spanner(self):
dpt = DynamicPickleType()
dialect = _make_dialect("sqlite")
Expand All @@ -352,6 +360,14 @@ def test_process_result_value_spanner(self):
result = dpt.process_result_value(pickled, dialect)
assert result == original

def test_process_result_value_mysql(self):
dpt = DynamicPickleType()
dialect = _make_dialect("mysql")
original = {"key": "value", "nums": [1, 2, 3]}
pickled = pickle.dumps(original)
result = dpt.process_result_value(pickled, dialect)
assert result == original

def test_process_result_value_non_spanner(self):
dpt = DynamicPickleType()
dialect = _make_dialect("sqlite")
Expand Down
Loading
Loading