Skip to content

Commit 541fe76

Browse files
axelsrzgithub-advanced-security[bot]Copilot
authored
Fastapi integration package (#176)
* Fastapi integration draft * Potential fix for code scanning alert no. 11: Information exposure through an exception Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update libraries/microsoft-agents-hosting-fastapi/microsoft_agents/hosting/fastapi/app/streaming/streaming_response.py Co-authored-by: Copilot <[email protected]> * Fastapi integration wip * Fastapi integration wip, emtpy agent working * Fastapi integration wip, auth agent bugfixing * Revert "1. TypingIndicator Concurrency & Argument fixes (#187)" This reverts commit 3024b7c. * Fastapi integration tentative, need to validate auth behavior * Fastapi integration tentative, need to validate auth behavior: nit fix * Reapply "1. TypingIndicator Concurrency & Argument fixes (#187)" This reverts commit e7f0cad. * Update test_samples/fastapi/README.md Co-authored-by: Copilot <[email protected]> * Update test_samples/fastapi/shared/__init__.py Co-authored-by: Copilot <[email protected]> * Update test_samples/fastapi/shared/github_api_client.py Co-authored-by: Copilot <[email protected]> * Update test_samples/fastapi/authorization_agent.py Co-authored-by: Copilot <[email protected]> * Fastapi integration tentative, need to validate auth behavior: copilot rreview fixers * Update test_samples/fastapi/shared/github_api_client.py Co-authored-by: Copilot <[email protected]> * Update libraries/microsoft-agents-hosting-fastapi/microsoft_agents/hosting/fastapi/app/streaming/streaming_response.py Co-authored-by: Copilot <[email protected]> * Update test_samples/fastapi/shared/cards.py Co-authored-by: Copilot <[email protected]> * Fastapi integration tentative, need to validate auth behavior: nit fix --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Copilot <[email protected]>
1 parent 9964b24 commit 541fe76

25 files changed

+1614
-1
lines changed

DevInstructions.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ Any issues should be filed on the [Agents-for-python](https://github.com/microso
3232
pip install -e ./libraries/microsoft-agents-hosting-aiohttp/ --config-settings editable_mode=compat
3333
pip install -e ./libraries/microsoft-agents-hosting-teams/ --config-settings editable_mode=compat
3434
pip install -e ./libraries/microsoft-agents-storage-blob/ --config-settings editable_mode=compat
35-
pip install -e ./libraries/microsoft-agents-storage-cosmos/ --config-settings editable_mode=compat
35+
pip install -e ./libraries/microsoft-agents-storage-cosmos/ --config-settings editable_mode=compat
36+
pip install -e ./libraries/microsoft-agents-hosting-fastapi/ --config-settings editable_mode=compat
3637
```
3738
1. Setup the dev dependencies for python. In the terminal, at the project root, run:
3839
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Microsoft Corporation.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from ._start_agent_process import start_agent_process
2+
from .agent_http_adapter import AgentHttpAdapter
3+
from .channel_service_route_table import channel_service_route_table
4+
from .cloud_adapter import CloudAdapter
5+
from .jwt_authorization_middleware import (
6+
JwtAuthorizationMiddleware,
7+
)
8+
from .app.streaming import (
9+
Citation,
10+
CitationUtil,
11+
StreamingResponse,
12+
)
13+
14+
__all__ = [
15+
"start_agent_process",
16+
"AgentHttpAdapter",
17+
"CloudAdapter",
18+
"JwtAuthorizationMiddleware",
19+
"channel_service_route_table",
20+
"Citation",
21+
"CitationUtil",
22+
"StreamingResponse",
23+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from typing import Optional
2+
from fastapi import Request, Response
3+
from microsoft_agents.hosting.core.app import AgentApplication
4+
from .cloud_adapter import CloudAdapter
5+
6+
7+
async def start_agent_process(
8+
request: Request,
9+
agent_application: AgentApplication,
10+
adapter: CloudAdapter,
11+
) -> Optional[Response]:
12+
"""Starts the agent host with the provided adapter and agent application.
13+
Args:
14+
adapter (CloudAdapter): The adapter to use for the agent host.
15+
agent_application (AgentApplication): The agent application to run.
16+
"""
17+
if not adapter:
18+
raise TypeError("start_agent_process: adapter can't be None")
19+
if not agent_application:
20+
raise TypeError("start_agent_process: agent_application can't be None")
21+
22+
# Start the agent application with the provided adapter
23+
return await adapter.process(
24+
request,
25+
agent_application,
26+
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from abc import abstractmethod
5+
from typing import Optional, Protocol
6+
7+
from fastapi import Request, Response
8+
9+
from microsoft_agents.hosting.core import Agent
10+
11+
12+
class AgentHttpAdapter(Protocol):
13+
@abstractmethod
14+
async def process(self, request: Request, agent: Agent) -> Optional[Response]:
15+
raise NotImplementedError()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .streaming import (
5+
Citation,
6+
CitationUtil,
7+
StreamingResponse,
8+
)
9+
10+
__all__ = [
11+
"Citation",
12+
"CitationUtil",
13+
"StreamingResponse",
14+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .citation import Citation
5+
from .citation_util import CitationUtil
6+
from .streaming_response import StreamingResponse
7+
8+
__all__ = [
9+
"Citation",
10+
"CitationUtil",
11+
"StreamingResponse",
12+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from typing import Optional
5+
from dataclasses import dataclass
6+
7+
8+
@dataclass
9+
class Citation:
10+
"""Citations returned by the model."""
11+
12+
content: str
13+
"""The content of the citation."""
14+
15+
title: Optional[str] = None
16+
"""The title of the citation."""
17+
18+
url: Optional[str] = None
19+
"""The URL of the citation."""
20+
21+
filepath: Optional[str] = None
22+
"""The filepath of the document."""
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import re
5+
from typing import List, Optional
6+
7+
from microsoft_agents.activity import ClientCitation
8+
9+
10+
class CitationUtil:
11+
"""Utility functions for manipulating text and citations."""
12+
13+
@staticmethod
14+
def snippet(text: str, max_length: int) -> str:
15+
"""
16+
Clips the text to a maximum length in case it exceeds the limit.
17+
18+
Args:
19+
text: The text to clip.
20+
max_length: The maximum length of the text to return, cutting off the last whole word.
21+
22+
Returns:
23+
The modified text
24+
"""
25+
if len(text) <= max_length:
26+
return text
27+
28+
snippet = text[:max_length]
29+
snippet = snippet[: min(len(snippet), snippet.rfind(" "))]
30+
snippet += "..."
31+
return snippet
32+
33+
@staticmethod
34+
def format_citations_response(text: str) -> str:
35+
"""
36+
Convert citation tags `[doc(s)n]` to `[n]` where n is a number.
37+
38+
Args:
39+
text: The text to format.
40+
41+
Returns:
42+
The formatted text.
43+
"""
44+
return re.sub(r"\[docs?(\d+)\]", r"[\1]", text, flags=re.IGNORECASE)
45+
46+
@staticmethod
47+
def get_used_citations(
48+
text: str, citations: List[ClientCitation]
49+
) -> Optional[List[ClientCitation]]:
50+
"""
51+
Get the citations used in the text. This will remove any citations that are
52+
included in the citations array from the response but not referenced in the text.
53+
54+
Args:
55+
text: The text to search for citation references, i.e. [1], [2], etc.
56+
citations: The list of citations to search for.
57+
58+
Returns:
59+
The list of citations used in the text.
60+
"""
61+
regex = re.compile(r"\[(\d+)\]", re.IGNORECASE)
62+
matches = regex.findall(text)
63+
64+
if not matches:
65+
return None
66+
67+
# Remove duplicates
68+
filtered_matches = set(matches)
69+
70+
# Add citations
71+
used_citations = []
72+
for match in filtered_matches:
73+
citation_ref = f"[{match}]"
74+
found = next(
75+
(
76+
citation
77+
for citation in citations
78+
if f"[{citation.position}]" == citation_ref
79+
),
80+
None,
81+
)
82+
if found:
83+
used_citations.append(found)
84+
85+
return used_citations if used_citations else None

0 commit comments

Comments
 (0)