Skip to content

Commit 06d3763

Browse files
authored
Merge pull request #27 from Azure-Samples/langchainupdates
Use structured response, remove buggy middleware
2 parents 9380f86 + 7f0e623 commit 06d3763

File tree

3 files changed

+96
-79
lines changed

3 files changed

+96
-79
lines changed

examples/langchainv1_mcp_github.py

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
import azure.identity
1616
from dotenv import load_dotenv
1717
from langchain.agents import create_agent
18-
from langchain.agents.middleware import AgentMiddleware, AgentState, ModelRequest
1918
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
2019
from langchain_mcp_adapters.client import MultiServerMCPClient
2120
from langchain_openai import AzureChatOpenAI, ChatOpenAI
21+
from pydantic import BaseModel, Field
22+
from rich import print
2223
from rich.logging import RichHandler
2324

2425
logging.basicConfig(level=logging.WARNING, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()])
@@ -54,21 +55,18 @@
5455
base_model = ChatOpenAI(model=os.getenv("OPENAI_MODEL", "gpt-4o-mini"))
5556

5657

57-
class ToolCallLimitMiddleware(AgentMiddleware):
58-
def __init__(self, limit) -> None:
59-
super().__init__()
60-
self.limit = limit
58+
class IssueProposal(BaseModel):
59+
"""Contact information for a person."""
6160

62-
def modify_model_request(self, request: ModelRequest, state: AgentState) -> ModelRequest:
63-
tool_call_count = sum(1 for msg in state["messages"] if isinstance(msg, AIMessage) and msg.tool_calls)
64-
if tool_call_count >= self.limit:
65-
logger.info("Tool call limit of %d reached, disabling further tool calls.", self.limit)
66-
request.tools = []
67-
return request
61+
url: str = Field(description="URL of the issue")
62+
title: str = Field(description="Title of the issue")
63+
summary: str = Field(description="Brief summary of the issue and signals for closing")
64+
should_close: bool = Field(description="Whether the issue should be closed or not")
65+
reply_message: str = Field(description="Message to post when closing the issue, if applicable")
6866

6967

7068
async def main():
71-
client = MultiServerMCPClient(
69+
mcp_client = MultiServerMCPClient(
7270
{
7371
"github": {
7472
"url": "https://api.githubcopilot.com/mcp/",
@@ -78,27 +76,26 @@ async def main():
7876
}
7977
)
8078

81-
tools = await client.get_tools()
79+
tools = await mcp_client.get_tools()
8280
tools = [t for t in tools if t.name in ("list_issues", "search_code", "search_issues", "search_pull_requests")]
83-
agent = create_agent(base_model, tools, middleware=[ToolCallLimitMiddleware(limit=5)])
8481

85-
stale_prompt_path = Path(__file__).parent / "staleprompt.md"
86-
with stale_prompt_path.open("r", encoding="utf-8") as f:
87-
stale_prompt = f.read()
88-
89-
user_content = stale_prompt + " Find one issue from Azure-samples azure-search-openai-demo that can be closed."
82+
prompt_path = Path(__file__).parent / "triager.prompt.md"
83+
with prompt_path.open("r", encoding="utf-8") as f:
84+
prompt = f.read()
85+
agent = create_agent(base_model, prompt=prompt, tools=tools, response_format=IssueProposal)
9086

87+
user_content = "Find an issue from Azure-samples azure-search-openai-demo that can be closed."
9188
async for step in agent.astream({"messages": [HumanMessage(content=user_content)]}, stream_mode="updates"):
9289
for step_name, step_data in step.items():
9390
last_message = step_data["messages"][-1]
9491
if isinstance(last_message, AIMessage) and last_message.tool_calls:
9592
tool_name = last_message.tool_calls[0]["name"]
9693
tool_args = last_message.tool_calls[0]["args"]
97-
logger.info(f"Calling tool '{tool_name}' with args: {tool_args}")
94+
logger.info(f"Calling tool '{tool_name}' with args:\n{tool_args}")
9895
elif isinstance(last_message, ToolMessage):
99-
logger.info(f"Got tool result: {step_data['messages'][-1].content[0:200]}...")
100-
else:
101-
logger.info(f"Response: {step_data['messages'][-1].content}")
96+
logger.info(f"Got tool result:\n{last_message.content[0:200]}...")
97+
if step_data.get("structured_response"):
98+
print(step_data["structured_response"])
10299

103100

104101
if __name__ == "__main__":

examples/staleprompt.md

Lines changed: 0 additions & 56 deletions
This file was deleted.

examples/triager.prompt.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
2+
# Issue Triager
3+
4+
You are a GitHub issue triage specialist tasked with finding an old stale issue from a GitHub repository and determining whether it can be closed.
5+
6+
## Steps
7+
8+
1. **Search for stale issues**: Use GitHub tools to list issues with "Stale" label, sorted by creation date (oldest first)
9+
2. **Examine each issue**: Get detailed information including:
10+
- Creation date and last update
11+
- Issue description and problem reported
12+
- Comments and any attempted solutions
13+
- Current relevance to the codebase
14+
3. **Search docs and repo**: Search the codebase (using search code tool and get file tool from GitHub MCP server) to see if code has changed in a way that resolves the issue. DO NOT make more than 8 tool calls total when doing research.
15+
4. **Categorize obsolescence**: Identify issues that are obsolete due to:
16+
- Infrastructure/deployment changes since the issue was reported
17+
- Migration to newer libraries/frameworks (e.g., OpenAI SDK updates)
18+
- Cross-platform compatibility improvements
19+
- Configuration system redesigns
20+
- API changes that resolve the underlying problem
21+
22+
### Forming Valid GitHub Code Search Queries (Important)
23+
24+
When you search the repository code to judge whether an issue is already resolved, follow these rules to avoid GitHub search API parsing errors (422). To guarantee never hitting 422 due to boolean grouping, you MUST NOT use `OR`, parentheses groups of literals, or compound boolean expressions. Always issue simple, single-term (plus qualifiers) queries sequentially and stop early.
25+
26+
1. Use proper qualifiers – `repo:OWNER/REPO`, `path:sub/dir`, `extension:py`, `language:python`.
27+
2. NEVER use `OR` (or `AND`, `NOT`, parentheses groups). If you have multiple synonyms or variants, run them as separate queries in priority order until you get sufficient evidence, then STOP.
28+
3. Narrow with `path:` and/or `extension:` whenever possible for relevance and speed.
29+
4. Combine exactly one content term (or quoted phrase) with qualifiers. Example: `repo:Azure-Samples/example-repo path:src "search_client"`.
30+
5. Qualifiers-only queries (to list files) are allowed: `repo:Azure-Samples/example-repo path:scripts extension:sh`.
31+
6. Enforce the total research budget (max 8 tool calls). Plan the minimal ordered list of single-term queries before executing.
32+
7. Provide plain text; the tool layer URL-encodes automatically.
33+
8. Avoid line numbers (`file.py:123`) or unrelated tokens—they are not supported.
34+
9. If a query unexpectedly fails (rare with this simplified pattern), simplify further: remove lowest-value qualifier (except `repo:`) or choose an alternative synonym.
35+
10. Prefer fewer decisive single-term queries over exploratory breadth.
36+
11. Treat casing variants as separate queries only if earlier queries returned zero results.
37+
38+
Decision mini–flow (apply top to bottom):
39+
1. List up to 4 highest-signal search terms (synonyms, old config keys, API names) mentally first.
40+
2. Execute the first single-term qualified query.
41+
3. If non-empty results: analyze; only run the next term if additional confirmation is required.
42+
4. If empty: run the next term.
43+
5. Stop immediately once you have enough information to assess the issue or you reach the tool call budget.
44+
45+
46+
Example valid queries (each a single request):
47+
* List markdown docs under `docs/`:
48+
`repo:Azure-Samples/example-repo path:docs extension:md`
49+
* Search for deprecated function:
50+
`repo:Azure-Samples/example-repo path:src "old_function_name"`
51+
* Check for config schema term:
52+
`repo:Azure-Samples/example-repo extension:yaml apiVersion`
53+
* Alternate synonym (run only if prior empty):
54+
`repo:Azure-Samples/example-repo extension:yaml schemaVersion`
55+
56+
Avoid (invalid – all banned patterns):
57+
`repo:Azure-Samples/example-repo path:docs (extension:md OR extension:txt)` (uses OR)
58+
`repo:Azure-Samples/example-repo ("apiVersion" OR "schemaVersion")` (parenthesized OR)
59+
`repo:Azure-Samples/example-repo path:docs extension:md OR extension:txt` (OR)
60+
`repo:Azure-Samples/example-repo script.py:120 extension:py` (line number)
61+
`repo:Azure-Samples/example-repo path:docs (.md OR .txt)` (bare extensions + OR)
62+
63+
If still ambiguous after sequential single-term searches, document absence and proceed—do NOT attempt a boolean query.
64+
65+
### Output Format
66+
67+
Once you've done enough research on the issue, provide the following information:
68+
69+
1. **Issue URL and Title**
70+
3. **Brief Summary** (2 sentences):
71+
- What the original problem was
72+
- Why it's now obsolete
73+
4. **Suggested Closing Reply**: A professional comment explaining:
74+
- Why the issue is being closed as obsolete
75+
- What changes have made it irrelevant (Only high confidence changes)
76+
- Invitation to open a new issue if the problem persists with current version

0 commit comments

Comments
 (0)