Add Go service/handler tests for API contract parity (#16905)

This commit is contained in:
euvre
2026-07-20 20:02:41 +08:00
committed by GitHub
parent 2f7c2eb53c
commit f45f03a016
46 changed files with 1553 additions and 214 deletions

View File

@@ -222,8 +222,8 @@ class RAGTools:
"them all together as one comma-separated list, in the SAME language as the question.\n"
' Example — question "In which year did Apple acquire Beats?": keywords = '
'"Apple, Apple Inc., acquire, acquisition, Beats" (terms from the question + synonyms; '
'the year is the ANSWER, so it must NOT appear).\n\n'
'Output ONLY JSON, no prose, no code fences: '
"the year is the ANSWER, so it must NOT appear).\n\n"
"Output ONLY JSON, no prose, no code fences: "
'{"question": "<standalone question>", "keywords": "<term1, term2, synonym1, ...>"}'
)
user = f"Conversation:\n{transcript}\n\nOutput JSON:"

View File

@@ -47,11 +47,19 @@ register_tool(
_inspector_schema("open_context", "Expand the original context around a chunk", {"chunk_id": {"type": "string"}, "width": {"type": "integer", "description": "Number of characters to expand"}}),
open_context,
)
register_tool("inspector_compare", _inspector_schema("compare_sources", "Compare multiple chunks to find common points and contradictions", {"chunk_ids": {"type": "array", "items": {"type": "string"}}}), compare_sources)
register_tool("inspector_grep_within", _inspector_schema("grep_within", "Search for an exact keyword or pattern within a document", {"doc_id": {"type": "string"}, "pattern": {"type": "string"}}), grep_within)
register_tool(
"inspector_compare",
_inspector_schema("compare_sources", "Compare multiple chunks to find common points and contradictions", {"chunk_ids": {"type": "array", "items": {"type": "string"}}}),
compare_sources,
)
register_tool(
"inspector_grep_within", _inspector_schema("grep_within", "Search for an exact keyword or pattern within a document", {"doc_id": {"type": "string"}, "pattern": {"type": "string"}}), grep_within
)
register_tool(
"inspector_request_adjacent",
_inspector_schema("request_adjacent", "Get adjacent entries before or after a chunk", {"chunk_id": {"type": "string"}, "direction": {"type": "string", "enum": ["prev", "next"]}, "count": {"type": "integer"}}),
_inspector_schema(
"request_adjacent", "Get adjacent entries before or after a chunk", {"chunk_id": {"type": "string"}, "direction": {"type": "string", "enum": ["prev", "next"]}, "count": {"type": "integer"}}
),
request_adjacent,
)

View File

@@ -33,6 +33,7 @@ def get_function_schemas(tool_names: list[str]) -> list[dict]:
# Common schema builders
def _search_schema(name: str, desc: str) -> dict:
return {
"type": "function",

View File

@@ -40,9 +40,7 @@ from typing import Callable
# Per-request sink: a callable(str) that forwards one log line, or None when no
# agentic turn is streaming in the current context.
_think_log_sink: contextvars.ContextVar[Callable[[str], None] | None] = contextvars.ContextVar(
"think_log_sink", default=None
)
_think_log_sink: contextvars.ContextVar[Callable[[str], None] | None] = contextvars.ContextVar("think_log_sink", default=None)
# Only bracket-tagged INFO lines from these logger namespaces are surfaced.
_SCOPED_PREFIXES = ("rag.advanced_rag", "rag.llm.chat_model", "rag.llm.tool_decorator")
@@ -68,7 +66,7 @@ class ThinkLogHandler(logging.Handler):
if not msg or not msg.lstrip().startswith("["):
return
try:
sink("<br>"+msg.strip())
sink("<br>" + msg.strip())
except Exception:
# Never let think-log forwarding break the request or the logging
# subsystem itself.

View File

@@ -423,8 +423,7 @@ def chunk(filename, binary=None, from_page=0, to_page=MAXIMUM_PAGE_NUMBER, lang=
elif re.search(r"\.docx$", filename, re.IGNORECASE):
docx_parser = Docx()
qai_list, tbls = docx_parser(filename, binary,
from_page=0, to_page=MAXIMUM_PAGE_NUMBER, callback=callback)
qai_list, tbls = docx_parser(filename, binary, from_page=0, to_page=MAXIMUM_PAGE_NUMBER, callback=callback)
res = tokenize_table(tbls, doc, eng, language=lang)
for i, (q, a, image) in enumerate(qai_list):
res.append(beAdocDocx(deepcopy(doc), q, a, eng, image, i))

View File

@@ -93,14 +93,10 @@ def _py_type_to_json(py_type: Any) -> dict[str, Any]:
_PARAM_RE = re.compile(r"^\s*:param\s+(?P<name>\w+)\s*:\s*(?P<desc>.*?)\s*$")
_GOOGLE_ARGS_HDR_RE = re.compile(r"^(Args|Arguments|Parameters)\s*:\s*$")
_GOOGLE_SECTION_HDR_RE = re.compile(
r"^(Returns?|Yields?|Raises|Notes?|Examples?|Attributes?|Todo|See Also|Warning|Warnings|Tip)\s*:\s*$"
)
_GOOGLE_SECTION_HDR_RE = re.compile(r"^(Returns?|Yields?|Raises|Notes?|Examples?|Attributes?|Todo|See Also|Warning|Warnings|Tip)\s*:\s*$")
# Google-style parameter line: leading indent, identifier, optional ``(type)``,
# then ``: description``. The description can be empty (continuation lines fill it).
_GOOGLE_PARAM_RE = re.compile(
r"^(?P<indent>\s+)(?P<name>\w+)\s*(?:\([^)]*\))?\s*:\s*(?P<desc>.*)$"
)
_GOOGLE_PARAM_RE = re.compile(r"^(?P<indent>\s+)(?P<name>\w+)\s*(?:\([^)]*\))?\s*:\s*(?P<desc>.*)$")
def _parse_param_docs(docstring: str | None) -> tuple[str, dict[str, str]]:
@@ -269,10 +265,7 @@ def tool(
# ``@tool`` (no parens) — ``fn`` is the function being decorated.
if fn is not None:
if not callable(fn):
raise TypeError(
"@tool used incorrectly. Use `@tool` or `@tool(timeout=N)`; "
f"got first positional argument of type {type(fn).__name__}."
)
raise TypeError(f"@tool used incorrectly. Use `@tool` or `@tool(timeout=N)`; got first positional argument of type {type(fn).__name__}.")
return decorate(fn)
# ``@tool(timeout=N)`` — return the decorator that will receive the function.
@@ -299,9 +292,7 @@ class FunctionToolSession:
self.tools_map: dict[str, Callable[..., Any]] = {}
for fn in tools:
if not is_tool(fn):
raise TypeError(
f"{getattr(fn, '__name__', fn)!r} is not a @tool-decorated callable"
)
raise TypeError(f"{getattr(fn, '__name__', fn)!r} is not a @tool-decorated callable")
self.tools_map[fn.openai_schema["function"]["name"]] = fn
@property
@@ -315,9 +306,7 @@ class FunctionToolSession:
if name not in self.tools_map:
raise KeyError(f"Tool {name!r} is not registered")
if not isinstance(arguments, Mapping):
raise TypeError(
f"Tool arguments for {name} must be an object, got {type(arguments).__name__}"
)
raise TypeError(f"Tool arguments for {name} must be an object, got {type(arguments).__name__}")
fn = self.tools_map[name]
logging.info(f"[Function tool] Running the {name} tool with: {str(arguments)[:200]}")
if asyncio.iscoroutinefunction(fn):
@@ -334,4 +323,4 @@ class FunctionToolSession:
# "wait forever" — ``asyncio.wait_for(..., timeout=None)`` handles it.
configured = getattr(fn, "_tool_timeout", _TIMEOUT_UNSET)
effective_timeout = request_timeout if configured is _TIMEOUT_UNSET else configured
return await asyncio.wait_for(coro, timeout=effective_timeout)
return await asyncio.wait_for(coro, timeout=effective_timeout)