Skip to content

Commit 516d132

Browse files
committed
feature: 增加 OpenAI 兼容模型适配器
- 新增 openai_adapter 目录,按模型拆分 DeepSeek 与 Hunyuan 的特殊适配逻辑 - 保持 OpenAIModel 主流程兼容,避免将特定模型逻辑继续堆叠在主实现中 - 适配 DeepSeek v4 的 thinking、response_format、reasoning_content 与 token usage 处理 - 为 hy3-preview 增加 ToolPrompt 文本工具调用解析与流式输出过滤 - 修复 ToolPrompt 流式解析多个工具调用时只保留最后一个的问题 - 优化 thinking 示例,仅在 hy3-preview 下启用 add_tools_to_prompt,并区分展示思考、工具调用和最终回复 - 更新示例 prompt,避免模型在工具返回前编造结果或过度展开推理
1 parent 933f5a5 commit 516d132

8 files changed

Lines changed: 575 additions & 32 deletions

File tree

examples/llmagent_with_thinking/agent/agent.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from trpc_agent_sdk.tools import FunctionTool
1212
from trpc_agent_sdk.types import GenerateContentConfig
1313
from trpc_agent_sdk.types import ThinkingConfig
14-
14+
from trpc_agent_sdk.types import HttpOptions
1515
from .config import get_model_config
1616
from .prompts import INSTRUCTION
1717
from .tools import get_weather_forecast
@@ -32,8 +32,9 @@ def _create_model() -> LLMModel:
3232
# if the LLM model service fails to return the JSON format of tool calls, you can also enable ToolPrompt.
3333
# This will prompt the LLM model to output the special text for tool calling in the main content,
3434
# thereby increasing the probability of successful tool invocation.
35-
# You can uncomment the code below to use ToolPrompt.
36-
# add_tools_to_prompt=True,
35+
# Thinking models may emit tool calls as text. ToolPrompt lets the
36+
# framework parse those text calls back into executable FunctionCalls.
37+
add_tools_to_prompt=model_name.lower() == "hy3-preview", # Enable ToolPrompt for Hy3-preview model
3738
)
3839
return model
3940

@@ -45,6 +46,9 @@ def create_agent():
4546
weather_tool = FunctionTool(get_weather_report)
4647
forecast_tool = FunctionTool(get_weather_forecast)
4748

49+
# Set reasoning effort to high for Hy3-preview model
50+
http_options=HttpOptions(extra_body={"chat_template_kwargs": {"reasoning_effort": "high"}})
51+
4852
return LlmAgent(
4953
name="weather_agent",
5054
description=
@@ -54,7 +58,7 @@ def create_agent():
5458
instruction=INSTRUCTION,
5559
tools=[weather_tool, forecast_tool],
5660
# Note: thinking_budget must be less than max_output_tokens
57-
generate_content_config=GenerateContentConfig(max_output_tokens=10240, ),
61+
generate_content_config=GenerateContentConfig(max_output_tokens=10240, http_options=http_options),
5862
# The model must be a thinking model to use this Planner; this configuration will not take effect for non-thinking models.
5963
planner=BuiltInPlanner(thinking_config=ThinkingConfig(
6064
include_thoughts=True,

examples/llmagent_with_thinking/agent/prompts.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,21 @@
1818
- Provide clear, useful weather information and suggestions
1919
2020
**Available tools:**
21-
1. `get_weather`: Get current weather information
21+
1. `get_weather_report`: Get current weather information
2222
2. `get_weather_forecast`: Get multi-day weather forecast
2323
2424
**Tool usage guide:**
25-
- When the user asks about the current weather, use `get_weather`
25+
- When the user asks about the current weather, use `get_weather_report`
2626
- When the user asks about the weather for the next few days, use `get_weather_forecast`
2727
- If the query is not clear, you can use both tools at the same time
28+
- Do not answer with weather data before the required tool result is available
29+
- Do not guess, simulate, or invent tool results
30+
- If a tool is needed, call the tool first and wait for the tool result before giving the final answer
31+
32+
**Thinking guidance:**
33+
- Keep reasoning concise and focused on choosing the right tool and city
34+
- Do not repeat the tool usage rules or tool schema in your reasoning
35+
- Do not draft the final answer in reasoning; use reasoning only to decide the next action
2836
2937
**Reply format:**
3038
- Provide accurate weather information

examples/llmagent_with_thinking/run_agent.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async def run_weather_agent():
3030
demo_queries = [
3131
"What's the weather like today?",
3232
"What's the current weather in Guangzhou?",
33-
"What will the weather be like in Shanghai for the next three days?",
33+
"Please check both the current weather in Guangzhou and the three-day weather forecast for Shanghai.",
3434
]
3535

3636
for query in demo_queries:
@@ -51,23 +51,78 @@ async def run_weather_agent():
5151

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

54-
print("🤖 Assistant: ", end="", flush=True)
54+
printed_thinking = False
55+
printed_assistant = False
56+
in_thinking = False
57+
thinking_line_start = False
58+
assistant_text_started = False
59+
60+
def print_assistant_header() -> None:
61+
nonlocal printed_assistant
62+
if printed_assistant:
63+
return
64+
if printed_thinking:
65+
print("\n")
66+
print("🤖 Assistant: ", end="", flush=True)
67+
printed_assistant = True
68+
69+
def print_thinking_header() -> None:
70+
nonlocal in_thinking, printed_thinking, thinking_line_start
71+
if in_thinking:
72+
return
73+
print("\n 💭 Thinking: ", end="", flush=True)
74+
in_thinking = True
75+
printed_thinking = True
76+
thinking_line_start = False
77+
78+
def print_thinking_text(text: str) -> None:
79+
nonlocal thinking_line_start
80+
for line in text.splitlines(keepends=True):
81+
if thinking_line_start:
82+
print(" ", end="", flush=True)
83+
print(line, end="", flush=True)
84+
thinking_line_start = line.endswith("\n")
85+
86+
def close_thinking_section() -> None:
87+
nonlocal in_thinking, thinking_line_start
88+
if in_thinking:
89+
if not thinking_line_start:
90+
print()
91+
print(" 💭 End Thinking")
92+
in_thinking = False
93+
thinking_line_start = False
94+
5595
async for event in runner.run_async(user_id=user_id, session_id=current_session_id, new_message=user_content):
5696
if not event.content or not event.content.parts:
5797
continue
5898

5999
if event.partial:
60100
for part in event.content.parts:
61101
if part.text:
62-
print(part.text, end="", flush=True)
102+
if part.thought:
103+
if assistant_text_started:
104+
continue
105+
print_thinking_header()
106+
print_thinking_text(part.text)
107+
else:
108+
close_thinking_section()
109+
print_assistant_header()
110+
assistant_text_started = True
111+
print(part.text, end="", flush=True)
63112
continue
64113

65114
for part in event.content.parts:
66-
if part.thought:
67-
continue
68-
if part.function_call:
115+
if part.thought and part.text and not printed_thinking and not assistant_text_started:
116+
print_thinking_header()
117+
print_thinking_text(part.text)
118+
elif part.function_call:
119+
close_thinking_section()
120+
print_assistant_header()
69121
print(f"\n🔧 [Invoke Tool:: {part.function_call.name}({part.function_call.args})]")
70122
elif part.function_response:
123+
close_thinking_section()
124+
printed_thinking = False
125+
print_assistant_header()
71126
print(f"📊 [Tool Result: {part.function_response.response}]")
72127
# elif part.text:
73128
# print(f"\n✅ {part.text}")

0 commit comments

Comments
 (0)