Tool argument errors put the argument values in the log message body, outside `lk.pii.*`
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 3/5
- 見積もり時間
- 半日
- 初心者へのやさしさ
- 25/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- python
調査の方向性
The message comes from prepare_function_arguments in livekit/agents/llm/utils.py, and the warning is logged in perform_tool_executions in voice/generation.py. Read telemetry/traces.py to see how lk.pii. fields are handled. Done means the ToolError warning logs a static message with the error text under a lk.pii. key. Pull request #7804 is already open against this issue, so check it before starting.
索引モデルが issue の本文から書いたものです。
説明
Bug Description
When a tool call's arguments can't be prepared, prepare_function_arguments raises a ToolError whose message includes the arguments:
- a validation error (valid JSON, wrong type) embeds the pydantic error text, which includes
input_value=...(code) - JSON that
json_repaircan't recover embeds the first 200 characters of the raw arguments (code, re-raised at L687)
perform_tool_executions then logs that message in the body of ToolError while executing tool: %s (code).
The same failure is also logged as error parsing arguments for ... with the arguments in lk.pii.arguments, so the value is marked there. The ToolError warning has no lk.pii. field, so the value is in the body, where it can't be dropped ("a log body cannot be redacted", traces.py). This is the same class as #7156 (participant identity in log bodies), at a call site that PR doesn't touch.
Expected Behavior
The argument values are not in the log body. For example, the warning logs a static message and puts the error text under a lk.pii. key, as error parsing arguments for ... already does. The ToolError returned to the model can keep the detail.
Reproduction Steps
Only livekit-agents, no audio or network. A scripted LLM returns one tool call, then a text reply, and the logs go through the CLI's JsonFormatter.
SECRET = "[email protected]"
@function_tool
async def lookup_orders(count: int) -> str:
"""Look up the customer's latest orders."""
return "ok"
# scripted LLM: first chat() returns
# FunctionToolCall(name="lookup_orders", arguments=json.dumps({"count": SECRET}), call_id="call_1")
# then a plain text reply
session = AgentSession(llm=ScriptedLLM())
await session.start(agent=Agent(instructions="test", tools=[lookup_orders]))
await session.run(user_input="show my orders")
Output (full script below):
== valid JSON, wrong type: '{"count": "[email protected]"}'
[ERROR] 'error parsing arguments for `lookup_orders`'
value in lk.pii.* fields: ['lk.pii.arguments'] | value in message body: False
[WARNING] 'ToolError while executing tool: Error parsing arguments for `lookup_or'
value in lk.pii.* fields: no | value in message body: True
== truncated JSON: '{"count": "[email protected]'
[WARNING] 'repaired malformed function-call JSON arguments'
value in lk.pii.* fields: ['lk.pii.raw_arguments', 'lk.pii.repaired'] | value in message body: False
[ERROR] 'error parsing arguments for `lookup_orders`'
value in lk.pii.* fields: ['lk.pii.arguments'] | value in message body: False
[WARNING] 'ToolError while executing tool: Error parsing arguments for `lookup_or'
value in lk.pii.* fields: no | value in message body: True
In the second case the SDK repairs the JSON, then validation fails the same way.
Full script
"""Minimal repro: a tool argument error puts the argument value in the log message body.
The SDK marks content-bearing log fields with an `lk.pii.` prefix so that redaction can drop them,
because a log body cannot be redacted. When a tool call's arguments fail validation (a string for an
`int` parameter), the pydantic error text, including `input_value=...`, goes into the ToolError
message. When the arguments are not valid JSON, the first 200 characters of the raw arguments go into
it. `voice/generation.py` then logs that message in the body of `ToolError while executing tool: %s`,
with no `lk.pii.` field, so dropping `lk.pii.*` does not remove the value.
Only livekit-agents is used (no audio, no network). A scripted LLM returns one tool call, then a plain
text reply. Logs are written with the CLI's JsonFormatter.
pip install "livekit-agents==1.8.6"
python repro.py
"""
import asyncio
import io
import json
import logging
from livekit.agents import Agent, AgentSession, function_tool, llm
from livekit.agents.cli.log import JsonFormatter
from livekit.agents.types import DEFAULT_API_CONNECT_OPTIONS
SECRET = "[email protected]" # stands in for user data the model put in the arguments
CASES = {
"valid JSON, wrong type": json.dumps({"count": SECRET}),
"truncated JSON": '{"count": "' + SECRET,
}
class ScriptedLLM(llm.LLM):
def __init__(self, arguments: str) -> None:
super().__init__()
self.calls = 0
self.arguments = arguments
def chat(self, *, chat_ctx, tools=None, conn_options=DEFAULT_API_CONNECT_OPTIONS, **kwargs):
self.calls += 1
return ScriptedStream(
self, chat_ctx=chat_ctx, tools=tools or [], conn_options=conn_options, turn=self.calls
)
class ScriptedStream(llm.LLMStream):
def __init__(self, llm_, *, turn, **kwargs) -> None:
super().__init__(llm_, **kwargs)
self._turn = turn
async def _run(self) -> None:
if self._turn == 1:
call = llm.FunctionToolCall(name="lookup_orders", arguments=self._llm.arguments, call_id="call_1")
delta = llm.ChoiceDelta(role="assistant", tool_calls=[call])
else:
delta = llm.ChoiceDelta(role="assistant", content="Sorry, could you repeat that?")
self._event_ch.send_nowait(llm.ChatChunk(id=f"chunk_{self._turn}", delta=delta))
@function_tool
async def lookup_orders(count: int) -> str:
"""Look up the customer's latest orders.
Args:
count: how many orders to return
"""
return "ok"
async def run_case(name: str, arguments: str) -> None:
print(f"== {name}: {arguments!r}")
out = io.StringIO()
handler = logging.StreamHandler(out)
handler.setFormatter(JsonFormatter())
root = logging.getLogger()
root.handlers = [handler]
root.setLevel(logging.INFO)
session = AgentSession(llm=ScriptedLLM(arguments))
await session.start(agent=Agent(instructions="test", tools=[lookup_orders]))
await session.run(user_input="show my orders")
await session.aclose()
lines = [json.loads(line) for line in out.getvalue().splitlines()]
for line in lines:
if SECRET not in json.dumps(line):
continue
marked = [k for k, v in line.items() if k.startswith("lk.pii.") and SECRET in json.dumps(v)]
in_body = SECRET in line.get("message", "")
print(f" [{line['level']}] {line['message'][:70]!r}")
print(f" value in lk.pii.* fields: {marked or 'no'} | value in message body: {in_body}")
leaked = [line for line in lines if SECRET in line.get("message", "")]
print(f" records with the value in the message body: {len(leaked)}")
async def main() -> None:
for name, arguments in CASES.items():
await run_case(name, arguments)
asyncio.run(main())
Operating System
Windows 11
Models Used
None (scripted LLM). In a real call this needs the model to send arguments that fail validation, for example a customer's email or phone number in a field of another type.
Package Versions
livekit-agents==1.8.6
python==3.13
The same code is on main and in [email protected].
Additional Context
We drop lk.pii.* from our stdout logs before they reach CloudWatch. In the tool path, this warning is where we found the value still getting through.
- 主要言語
- Python
- スター
- 14.6k
- フォーク
- 3.9k
- 平均マージ
- 3日 3時間
- マージ済み PR(30日)
- 171
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
livekit/agents のほかの issue
-
Assistant markup is stripped from chat history when expressive mode is off対応中かも @ShivamSharma43 が 7 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
docs: openai.LLM vs openai.responses.LLM for HTTP OpenAI-compat hosts再び着手できるかも @AbarnaaSree が 47 日前に担当しましたが、オープン中のプルリクエストはありません。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
livekit/agents#7022 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
google: support Live Avatar video output for Gemini 3.8 Live (avatar_config, VIDEO modality)対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 5/5 1週間以上 初心者へのやさしさ 22/100
livekit/agents#7843 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 40/100
メンテナーはふだん 1 日以内に返信
-
StreamPacerWrapper.end_input() sets _input_ended before buffered sentences reach _sentences; last sentence(s) silently never sent to TTS対応中かも @Hugo-DDT が 1 日前に担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 25/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
jbacalso24/mimry#291 ·
メンテナーはふだん 1 日以内に返信
-
good first issue
難易度 2/5 1時間未満 初心者へのやさしさ 82/100
eli-alvarado/us-watchlist-alerts#14 ·
メンテナーはふだん 1 日以内に返信
-
from-review-extraction python severity:nit
難易度 2/5 1〜3時間 初心者へのやさしさ 80/100
LearningCircuit/local-deep-research#7462 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
cathrynlavery/diagram-design#441 ·
メンテナーはふだん 2 日以内に返信
-
[Cleanup] Include the public header in work_split.cpp対応中かも @blozano-tt が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
tenstorrent/tt-metal#60850 ·
メンテナーはふだん 1 日以内に返信