Category: Tech News

  • New Mexico state IDs can now be added to digital wallets

    Ian Carlos Campbell

    New Mexico is joining states like California and Ohio in supporting digital driver’s licenses and state IDs in Apple Wallet and Google Wallet. New Mexico residents are still required by law to carry their physical IDs for law enforcement and age-verification use, but at businesses and TSA checkpoints that have adopted New Mexico’s NM Verifier app, you’ll be able to tap your smartphone rather than pull out a card.

    Digital licenses can be added to Apple and Google’s apps now by scanning the front and back of your physical ID, capturing a scan of your face and submitting your digital application to be verified. Once added, digital IDs can be pulled up and used in the same way you’d pay with a digital payment card. Google has published a video on how to add your card to Google Wallet and Apple has a detailed support article on how to add IDs to Apple Wallet, if you’re looking for tips.

    The New Mexico Motor Vehicle Division first proposed legislation to “offer electronic credentials to customers at no additional cost” in January 2024. The rollout of digital IDs continues to be a complex process, with the TSA listing 12 states currently offering some form of digital identification, but only Arizona, California, Colorado, Georgia, Maryland, and New Mexico currently offering IDs for both Google and Apple’s apps. To make things more confusing, some states like Ohio, Hawaii, and Iowa offer IDs for Apple Wallet but not Google’s app.

    The shift to an exclusively digital wallet isn’t without potential risks, as well. The American Civil Liberties Union has criticized states’ quick adoption of digital driver’s licenses without putting in place additional protections for cardholders. Those include things like preventing ID issuers and verifiers from tracking the usage of digital cards and preserving the right to not use a digital card for anyone who doesn’t own or can’t afford a smartphone. Apple and Google’s solutions offer privacy protections in terms of encrypting information and keeping your device locked even after you’ve presented your ID, but they don’t totally account for how state or federal governments could access these new digital systems.

    This article originally appeared on Engadget at https://www.engadget.com/apps/new-mexico-state-ids-can-now-be-added-to-digital-wallets-211549853.html?src=rss

    Go Here to Read this Fast! New Mexico state IDs can now be added to digital wallets

    Originally appeared here:
    New Mexico state IDs can now be added to digital wallets

  • PUBG creator Brendan Greene just announced a handful of new games

    Lawrence Bonk

    Brendan Greene is largely credited with making the iconic PlayerUnknown’s Battlegrounds, also known as PUBG, and inventing the entire battle royale genre. We knew he left PUBG’s publisher to form an independent studio back in 2021, but now we know what he’s been working on the past few years.

    His studio, PlayerUnknown Productions, is prepping a handful of titles, which it refers to as “an ambitious three-game plan.” First up, there’s Prologue: Go Wayback!, which is described as “a single-player open-world emergent game within the survival genre.” It uses the developer’s “in-house machine-learning-driven terrain generation technology” to create “millions of maps.” These maps are also easy on the eyes, as seen below.

    A screenshot showing a forested area.
    PlayerUnknown Productions

    We don’t know much about the gameplay, beyond the description and the aforementioned terrain generation technology. However, it’s available to wishlist right now on Steam. The company says it’ll launch as an early access title sometime in the first half of next year, following a series of playtests.

    There’s also a nifty-looking tech demo called Preface: Undiscovered World. It’s free and available to download right now. This demo is being released to showcase the company’s in-house game engine, called Melba. Preface allows players to explore an “Earth-scale world generated in real-time.”

    A desert area.
    PlayerUnknown Productions

    Greene says that this “digital planet is still quite empty for now, but every person who enters it and shares their feedback, contributes to its future development.” To that end, the Melba engine will be used to develop other games in the future.

    One such future game is called Artemis, which is described as a “massive multiplayer sandbox experience.” We don’t know a whole lot about the title, except that it’s likely years away. Greene says that his company will be releasing two unannounced games after Prologue: Go Wayback! that will each address “critical technical challenges” that will help with the development of the more ambitious Artemis. In any event, it looks like PlayerUnknown Productions is certainly off to a promising start.

    This article originally appeared on Engadget at https://www.engadget.com/gaming/pc/pubg-creator-brendan-greene-just-announced-a-handful-of-new-games-210053256.html?src=rss

    Go Here to Read this Fast! PUBG creator Brendan Greene just announced a handful of new games

    Originally appeared here:
    PUBG creator Brendan Greene just announced a handful of new games

  • Waymo announces it’s expanding to Miami

    Danny Gallagher

    Get ready to have that Will Smith song stuck in your head for the rest of the day because the autonomous taxi company Waymo is going to Miami. Yeah, sorry about that.

    Waymo announced its plans to Miami on its official Waypoint blog. The expansion will start early next year as the company gets its fleet of self-driving Jaguar I-PACE EVs familiar with Miami’s streets and intersections. Then in 2026, Waymo plans to start offering rides to customers through the Waymo One app.

    Waymo is also partnering with the African startup Moove as part of its expansion plans. Moove provides vehicles for ride-sharing services. Waymo wants Moove to manage its “fleet operations, facilities and charging infrastructure” first in Phoenix and eventually in Miami.

    The Waymo One app currently operates in parts of San Francisco, Phoenix, Los Angeles and Austin, according to Google support. Waymo secured $5.6 billion in funding in October to expand to Austin and Atlanta by the early part of next year.

    This article originally appeared on Engadget at https://www.engadget.com/transportation/waymo-announces-its-expanding-to-miami-204504533.html?src=rss

    Go Here to Read this Fast!

    Waymo announces it’s expanding to Miami

    Originally appeared here:

    Waymo announces it’s expanding to Miami

  • Threads is testing post analytics

    Karissa Bell

    Threads’ latest test could help creators and others understand more about how their posts are performing on the platform. The company is testing an expanded version of its analytics feature, which will show users stats for specific posts, Adam Mosseri said in an update.

    Up to now, Threads has had an “insights” feature, but it showed aggregated stats for all posts, so it was hard to discern which posts were performing well. Now, insights will be able to surface detailed metrics around specific posts, including views and interactions. It will also break down performance among followers and non-followers.

    “Now that your posts will be shown to more people who follow you, it’s especially important to understand what’s resonating with your existing audience,” Mosseri wrote. Threads recently updated its highly criticized “for you” algorithm to surface more posts from accounts you follow, rather than random unconnected accounts.

    The change could also address criticism from creators on Threads, who have said they often don’t understand how the app’s algorithm works. More detailed analytics could also help Meta entice more brands to the app as the company reportedly is gearing up to begin running ads on the service as soon as next month.

    This article originally appeared on Engadget at https://www.engadget.com/social-media/threads-is-testing-post-analytics-203548697.html?src=rss

    Go Here to Read this Fast! Threads is testing post analytics

    Originally appeared here:
    Threads is testing post analytics

  • Building a Fantasy Football Research Agent with LangGraph

    Evan Diewald

    An end-to-end guide covering integration with the Sleeper API, creation of a Streamlit UI, and deployment via AWS CDK

    Photo by Dmitriy Demidov on Unsplash

    It’s embarrassing how much time I spend thinking about my fantasy football team.

    Managing a squad means processing a firehose of information — injury reports, expert projections, upcoming bye weeks, and favorable matchups. And it’s not just the volume of data, but the ephermerality— if your star RB tweaks a hamstring during Wednesday practice, you better not be basing lineup decisions off of Tuesday’s report.

    This is why general-purpose chatbots like Anthropic’s Claude and OpenAI’s ChatGPT are essentially useless for fantasy football recommendations, as they are limited to a static training corpus that cuts off months, even years ago.

    For instance, if we ask Claude Sonnet 3.5 who the current best running back is, we see names like Christian McCaffrey, Breece Hall, and Travis Etienne, who have had injury-ridden or otherwise disappointing seasons thus far in 2024. There is no mention of Saquon Barkley or Derrick Henry, the obvious frontrunners at this stage. (Though to Claude’s credit, it discloses its limitations.)

    Apps like Perplexity are more accurate because they do access a search engine with up-to-date information. However, it of course has no knowledge of my entire roster situation, the state of our league’s playoff picture, or the nuances of our keeper rules.

    There is an opportunity to tailor a fantasy football-focused Agent with tools and personalized context for each user.

    Let’s dig into the implementation.

    Architecture Overview

    The heart of the chatbot will be a LangGraph Agent based on the ReAct framework. We’ll give it access to tools that integrate with the Sleeper API for common operations like checking the league standings, rosters, player stats, expert analysis, and more.

    In addition to the LangGraph API server, our backend will include a small Postgres database and Redis cache, which are used to manage state and route requests. We’ll use Streamlit for a simple, but effective UI.

    For development, we can run all of these components locally via Docker Compose, but I’ll also show the infrastructure-as-code (IaC) to deploy a scalable stack with AWS CDK.

    Sleeper API Integration

    Sleeper graciously exposes a public, read-only API that we can tap into for user & league details, including a full list of players, rosters, and draft information. Though it’s not documented explicitly, I also found some GraphQL endpoints that provide critical statistics, projections, and — perhaps most valuable of all — recent expert analysis by NFL reporters.

    I created a simple API client to access the various methods, which you can find here. The one trick that I wanted to highlight is the requests-cache library. I don’t want to be a greedy client of Sleeper’s freely-available datasets, so I cache responses in a local Sqlite database with a basic TTL mechanism.

    Not only does this lessen the amount redundant API traffic bombarding Sleeper’s servers (reducing the chance that they blacklist my IP address), but it significantly reduces latency for my clients, making for a better UX.

    Setting up and using the cache is dead simple, as you can see in this snippet —

    import requests_cache
    from urllib.parse import urljoin
    from typing import Union, Optional
    from pathlib import Path


    class SleeperClient:
    def __init__(self, cache_path: str = '../.cache'):

    # config
    self.cache_path = cache_path
    self.session = requests_cache.CachedSession(
    Path(cache_path) / 'api_cache',
    backend='sqlite',
    expire_after=60 * 60 * 24,
    )

    ...

    def _get_json(self, path: str, base_url: Optional[str] = None) -> dict:
    url = urljoin(base_url or self.base_url, path)
    return self.session.get(url).json()

    def get_player_stats(self, player_id: Union[str, int], season: Optional[int] = None, group_by_week: bool = False):
    return self._get_json(
    f'stats/nfl/player/{player_id}?season_type=regular&season={season or self.nfl_state["season"]}{"&grouping=week" if group_by_week else ""}',
    base_url=self.stats_url,
    )

    So running something like

    self.session.get(url)

    first checks the local Sqlite cache for an unexpired response that particular request. If it’s found, we can skip the API call and just read from the database.

    Defining the Tools

    I want to turn the Sleeper API client into a handful of key functions that the Agent can use to inform its responses. Because these functions will effectively be invoked by the LLM, I find it important to annotate them clearly and ask for simple, flexible arguments.

    For example, Sleeper’s API’s generally ask for numeric player id’s, which makes sense for a programmatic interface. However, I want to abstract that concept away from the LLM and just have it input player names for these functions. To ensure some additional flexibility and allow for things like typos, I implemented a basic “fuzzy search” method to map player name searches to their associated player id.

    # file: fantasy_chatbot/league.py

    def get_player_id_fuzzy_search(self, player_name: str) -> tuple[str, str]:
    # will need a simple search engine to go from player name to player id without needing exact matches. returns the player_id and matched player name as a tuple
    nearest_name = process.extract(query=player_name, choices=self.player_names, scorer=fuzz.WRatio, limit=1)[0]
    return self.player_name_to_id[nearest_name[0]], self.player_names[nearest_name[2]]

    # example usage in a tool
    def get_player_news(self, player_name: Annotated[str, "The player's name."]) -> str:
    """
    Get recent news about a player for the most up-to-date analysis and injury status.
    Use this whenever naming a player in a potential deal, as you should always have the right context for a recommendation.
    If sources are provided, include markdown-based link(s)
    (e.g. [Rotoballer](https://www.rotoballer.com/player-news/saquon-barkley-has-historic-night-sunday/1502955) )
    at the bottom of your response to provide proper attribution
    and allow the user to learn more.
    """
    player_id, player_name = self.get_player_id_fuzzy_search(player_name)
    # news
    news = self.client.get_player_news(player_id, limit=3)
    player_news = f"Recent News about {player_name}nn"
    for n in news:
    player_news += f"**{n['metadata']['title']}**n{n['metadata']['description']}"
    if analysis := n['metadata'].get('analysis'):
    player_news += f"nnAnalysis:n{analysis}"
    if url := n['metadata'].get('url'):
    # markdown link to source
    player_news += f"n[{n['source'].capitalize()}]({url})nn"

    return player_news

    This is better than a simple map of name to player id because it allows for misspellings and other typos, e.g. saquon → Saquon Barkley

    I created a number of useful tools based on these principles:

    • Get League Status (standings, current week, no. playoff teams, etc.)
    • Get Roster for Team Owner
    • Get Player News (up-to-date articles / analysis about the player)
    • Get Player Stats (weekly points scored this season with matchups)
    • Get Player Current Owner (critical for proposing trades)
    • Get Best Available at Position (the waiver wire)
    • Get Player Rankings (performance so far, broken down by position)

    You can probably think of a few more functions that would be useful to add, like details about recent transactions, league head-to-heads, and draft information.

    LangGraph Agent

    The impetus for this entire project was an opportunity to learn the LangGraph ecosystem, which may be becoming the de facto standard for constructing agentic workflows.

    I’ve hacked together agents from scratch in the past, and I wish I had known about LangGraph at the time. It’s not just a thin wrapper around the various LLM providers, it provides immense utility for building, deploying, & monitoring complex workflows. I’d encourage you to check out the Introduction to LangGraph course by LangChain Academy if you’re interested in diving deeper.

    As mentioned before, the graph itself is based on the ReAct framework, which is a popular and effective way to get LLM’s to interact with external tools like those defined above.

    I’ve also added a node to persist long-term memories about each user, so that information can be persisted across sessions. I want our agent to “remember” things like users’ concerns, preferences, and previously-recommended trades, as this is not a feature that is implemented particularly well in the chatbots I’ve seen. In graph form, it looks like this:

    Pretty simple right? Again, you can checkout the full graph definition in the code, but I’ll highlight the write_memory node, which is responsible for writing & updating a profile for each user. This allows us to track key interactions while being efficient about token use.

    def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
    """Reflect on the chat history and save a memory to the store."""

    # get the username from the config
    username = config["configurable"]["username"]

    # retrieve existing memory if available
    namespace = ("memory", username)
    existing_memory = store.get(namespace, "user_memory")

    # format the memories for the instruction
    if existing_memory and existing_memory.value:
    memory_dict = existing_memory.value
    formatted_memory = (
    f"Team Name: {memory_dict.get('team_name', 'Unknown')}n"
    f"Current Concerns: {memory_dict.get('current_concerns', 'Unknown')}"
    f"Other Details: {memory_dict.get('other_details', 'Unknown')}"
    )
    else:
    formatted_memory = None

    system_msg = CREATE_MEMORY_INSTRUCTION.format(memory=formatted_memory)

    # invoke the model to produce structured output that matches the schema
    new_memory = llm_with_structure.invoke([SystemMessage(content=system_msg)] + state['messages'])

    # overwrite the existing user profile
    key = "user_memory"
    store.put(namespace, key, new_memory)

    These memories are surfaced in the system prompt, where I also gave the LLM basic details about our league and how I want it to handle common user requests.

    Streamlit UI and Demo

    I’m not a frontend developer, so the UI leans heavily on Streamlit’s components and familiar chatbot patterns. Users input their Sleeper username, which is used to lookup their available leagues and persist memories across threads.

    I also added a couple of bells and whistles, like implementing token streaming so that users get instant feedback from the LLM. The other important piece is a “research pane”, which surfaces the results of the Agent’s tool calls so that user can inspect the raw data that informs each response.

    Here’s a quick demo.

    Deployment

    For development, I recommend deploying the components locally via the provided docker-compose.yml file. This will expose the API locally at http://localhost:8123 , so you can rapidly test changes and connect to it from a local Streamlit app.

    I have also included IaC for an AWS CDK-based deployment that I use to host the app on the internet. Most of the resources are defined here. Notice the parallels between the docker-compose.yml and the CDK code related to the ECS setup:

    Snippet from docker-compose.yml for the LangGraph API container:

    # from docker-compose.yml

    langgraph-api:
    image: "fantasy-chatbot"
    ports:
    - "8123:8000"
    healthcheck:
    test: curl --request GET --url http://localhost:8000/ok
    timeout: 1s
    retries: 5
    interval: 5s
    depends_on:
    langgraph-redis:
    condition: service_healthy
    langgraph-postgres:
    condition: service_healthy
    env_file: "../.env"
    environment:
    REDIS_URI: redis://langgraph-redis:6379
    POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable// file: fantasy-football-agent-stack.ts

    And here is the analogous setup in the CDK stack:

    // fantasy-football-agent-stack.ts

    const apiImageAsset = new DockerImageAsset(this, 'apiImageAsset', {
    directory: path.join(__dirname, '../../fantasy_chatbot'),
    file: 'api.Dockerfile',
    platform: assets.Platform.LINUX_AMD64,
    });
    const apiContainer = taskDefinition.addContainer('langgraph-api', {
    containerName: 'langgraph-api',
    image: ecs.ContainerImage.fromDockerImageAsset(apiImageAsset),
    portMappings: [{
    containerPort: 8000,
    }],
    environment: {
    ...dotenvMap,
    REDIS_URI: 'redis://127.0.0.1:6379',
    POSTGRES_URI: 'postgres://postgres:[email protected]:5432/postgres?sslmode=disable'
    },
    logging: ecs.LogDrivers.awsLogs({
    streamPrefix: 'langgraph-api',
    }),
    });

    apiContainer.addContainerDependencies(
    {
    container: redisContainer,
    condition: ecs.ContainerDependencyCondition.HEALTHY,
    },
    {
    container: postgresContainer,
    condition: ecs.ContainerDependencyCondition.HEALTHY,
    },
    )

    Aside from some subtle differences, it’s effectively a 1:1 translation, which is always something I look for when comparing local environments to “prod” deployments. The DockerImageAsset is a particularly useful resource, as it handles building and deploying (to ECR) the Docker image during synthesis.

    Note: Deploying the stack to your AWS account via npm run cdk deploy WILL incur charges. In this demo code I have not included any password protection on the Streamlit app, meaning anyone who has the URL can use the chatbot! I highly recommend adding some additional security if you plan to deploy it yourself.

    Takeaways

    You want to keep your tools simple. This app does a lot, but is still missing some key functionality, and it will start to break down if I simply add more tools. In the future, I want to break up the graph into task-specific sub-components, e.g. a “News Analyst” Agent and a “Statistician” Agent.

    Traceability and debugging are more important with Agent-based apps than traditional software. Despite significant advancements in models’ ability to produce structured outputs, LLM-based function calling is still inherently less reliable than conventional programs. I used LangSmith extensively for debugging.

    In an age of commoditized language models, there is no replacement for reliable reporters. We’re at a point where you can put together a reasonable chatbot in a weekend, so how do products differentiate themselves and build moats? This app (or any other like it) would be useless without access to high-quality reporting from analysts and experts. In other words, the Ian Rapaport’s and Matthew Berry’s of the world are more valuable than ever.

    Repo

    GitHub – evandiewald/fantasy-football-agent

    All images, unless otherwise noted, are by the author.


    Building a Fantasy Football Research Agent with LangGraph was originally published in Towards Data Science on Medium, where people are continuing the conversation by highlighting and responding to this story.

    Originally appeared here:
    Building a Fantasy Football Research Agent with LangGraph

    Go Here to Read this Fast! Building a Fantasy Football Research Agent with LangGraph

  • Building a Fantasy Football Research Agent with LangGraph

    Evan Diewald

    An end-to-end guide covering integration with the Sleeper API, creation of a Streamlit UI, and deployment via AWS CDK

    Photo by Dmitriy Demidov on Unsplash

    It’s embarrassing how much time I spend thinking about my fantasy football team.

    Managing a squad means processing a firehose of information — injury reports, expert projections, upcoming bye weeks, and favorable matchups. And it’s not just the volume of data, but the ephermerality— if your star RB tweaks a hamstring during Wednesday practice, you better not be basing lineup decisions off of Tuesday’s report.

    This is why general-purpose chatbots like Anthropic’s Claude and OpenAI’s ChatGPT are essentially useless for fantasy football recommendations, as they are limited to a static training corpus that cuts off months, even years ago.

    For instance, if we ask Claude Sonnet 3.5 who the current best running back is, we see names like Christian McCaffrey, Breece Hall, and Travis Etienne, who have had injury-ridden or otherwise disappointing seasons thus far in 2024. There is no mention of Saquon Barkley or Derrick Henry, the obvious frontrunners at this stage. (Though to Claude’s credit, it discloses its limitations.)

    Apps like Perplexity are more accurate because they do access a search engine with up-to-date information. However, it of course has no knowledge of my entire roster situation, the state of our league’s playoff picture, or the nuances of our keeper rules.

    There is an opportunity to tailor a fantasy football-focused Agent with tools and personalized context for each user.

    Let’s dig into the implementation.

    Architecture Overview

    The heart of the chatbot will be a LangGraph Agent based on the ReAct framework. We’ll give it access to tools that integrate with the Sleeper API for common operations like checking the league standings, rosters, player stats, expert analysis, and more.

    In addition to the LangGraph API server, our backend will include a small Postgres database and Redis cache, which are used to manage state and route requests. We’ll use Streamlit for a simple, but effective UI.

    For development, we can run all of these components locally via Docker Compose, but I’ll also show the infrastructure-as-code (IaC) to deploy a scalable stack with AWS CDK.

    Sleeper API Integration

    Sleeper graciously exposes a public, read-only API that we can tap into for user & league details, including a full list of players, rosters, and draft information. Though it’s not documented explicitly, I also found some GraphQL endpoints that provide critical statistics, projections, and — perhaps most valuable of all — recent expert analysis by NFL reporters.

    I created a simple API client to access the various methods, which you can find here. The one trick that I wanted to highlight is the requests-cache library. I don’t want to be a greedy client of Sleeper’s freely-available datasets, so I cache responses in a local Sqlite database with a basic TTL mechanism.

    Not only does this lessen the amount redundant API traffic bombarding Sleeper’s servers (reducing the chance that they blacklist my IP address), but it significantly reduces latency for my clients, making for a better UX.

    Setting up and using the cache is dead simple, as you can see in this snippet —

    import requests_cache
    from urllib.parse import urljoin
    from typing import Union, Optional
    from pathlib import Path


    class SleeperClient:
    def __init__(self, cache_path: str = '../.cache'):

    # config
    self.cache_path = cache_path
    self.session = requests_cache.CachedSession(
    Path(cache_path) / 'api_cache',
    backend='sqlite',
    expire_after=60 * 60 * 24,
    )

    ...

    def _get_json(self, path: str, base_url: Optional[str] = None) -> dict:
    url = urljoin(base_url or self.base_url, path)
    return self.session.get(url).json()

    def get_player_stats(self, player_id: Union[str, int], season: Optional[int] = None, group_by_week: bool = False):
    return self._get_json(
    f'stats/nfl/player/{player_id}?season_type=regular&season={season or self.nfl_state["season"]}{"&grouping=week" if group_by_week else ""}',
    base_url=self.stats_url,
    )

    So running something like

    self.session.get(url)

    first checks the local Sqlite cache for an unexpired response that particular request. If it’s found, we can skip the API call and just read from the database.

    Defining the Tools

    I want to turn the Sleeper API client into a handful of key functions that the Agent can use to inform its responses. Because these functions will effectively be invoked by the LLM, I find it important to annotate them clearly and ask for simple, flexible arguments.

    For example, Sleeper’s API’s generally ask for numeric player id’s, which makes sense for a programmatic interface. However, I want to abstract that concept away from the LLM and just have it input player names for these functions. To ensure some additional flexibility and allow for things like typos, I implemented a basic “fuzzy search” method to map player name searches to their associated player id.

    # file: fantasy_chatbot/league.py

    def get_player_id_fuzzy_search(self, player_name: str) -> tuple[str, str]:
    # will need a simple search engine to go from player name to player id without needing exact matches. returns the player_id and matched player name as a tuple
    nearest_name = process.extract(query=player_name, choices=self.player_names, scorer=fuzz.WRatio, limit=1)[0]
    return self.player_name_to_id[nearest_name[0]], self.player_names[nearest_name[2]]

    # example usage in a tool
    def get_player_news(self, player_name: Annotated[str, "The player's name."]) -> str:
    """
    Get recent news about a player for the most up-to-date analysis and injury status.
    Use this whenever naming a player in a potential deal, as you should always have the right context for a recommendation.
    If sources are provided, include markdown-based link(s)
    (e.g. [Rotoballer](https://www.rotoballer.com/player-news/saquon-barkley-has-historic-night-sunday/1502955) )
    at the bottom of your response to provide proper attribution
    and allow the user to learn more.
    """
    player_id, player_name = self.get_player_id_fuzzy_search(player_name)
    # news
    news = self.client.get_player_news(player_id, limit=3)
    player_news = f"Recent News about {player_name}nn"
    for n in news:
    player_news += f"**{n['metadata']['title']}**n{n['metadata']['description']}"
    if analysis := n['metadata'].get('analysis'):
    player_news += f"nnAnalysis:n{analysis}"
    if url := n['metadata'].get('url'):
    # markdown link to source
    player_news += f"n[{n['source'].capitalize()}]({url})nn"

    return player_news

    This is better than a simple map of name to player id because it allows for misspellings and other typos, e.g. saquon → Saquon Barkley

    I created a number of useful tools based on these principles:

    • Get League Status (standings, current week, no. playoff teams, etc.)
    • Get Roster for Team Owner
    • Get Player News (up-to-date articles / analysis about the player)
    • Get Player Stats (weekly points scored this season with matchups)
    • Get Player Current Owner (critical for proposing trades)
    • Get Best Available at Position (the waiver wire)
    • Get Player Rankings (performance so far, broken down by position)

    You can probably think of a few more functions that would be useful to add, like details about recent transactions, league head-to-heads, and draft information.

    LangGraph Agent

    The impetus for this entire project was an opportunity to learn the LangGraph ecosystem, which may be becoming the de facto standard for constructing agentic workflows.

    I’ve hacked together agents from scratch in the past, and I wish I had known about LangGraph at the time. It’s not just a thin wrapper around the various LLM providers, it provides immense utility for building, deploying, & monitoring complex workflows. I’d encourage you to check out the Introduction to LangGraph course by LangChain Academy if you’re interested in diving deeper.

    As mentioned before, the graph itself is based on the ReAct framework, which is a popular and effective way to get LLM’s to interact with external tools like those defined above.

    I’ve also added a node to persist long-term memories about each user, so that information can be persisted across sessions. I want our agent to “remember” things like users’ concerns, preferences, and previously-recommended trades, as this is not a feature that is implemented particularly well in the chatbots I’ve seen. In graph form, it looks like this:

    Pretty simple right? Again, you can checkout the full graph definition in the code, but I’ll highlight the write_memory node, which is responsible for writing & updating a profile for each user. This allows us to track key interactions while being efficient about token use.

    def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
    """Reflect on the chat history and save a memory to the store."""

    # get the username from the config
    username = config["configurable"]["username"]

    # retrieve existing memory if available
    namespace = ("memory", username)
    existing_memory = store.get(namespace, "user_memory")

    # format the memories for the instruction
    if existing_memory and existing_memory.value:
    memory_dict = existing_memory.value
    formatted_memory = (
    f"Team Name: {memory_dict.get('team_name', 'Unknown')}n"
    f"Current Concerns: {memory_dict.get('current_concerns', 'Unknown')}"
    f"Other Details: {memory_dict.get('other_details', 'Unknown')}"
    )
    else:
    formatted_memory = None

    system_msg = CREATE_MEMORY_INSTRUCTION.format(memory=formatted_memory)

    # invoke the model to produce structured output that matches the schema
    new_memory = llm_with_structure.invoke([SystemMessage(content=system_msg)] + state['messages'])

    # overwrite the existing user profile
    key = "user_memory"
    store.put(namespace, key, new_memory)

    These memories are surfaced in the system prompt, where I also gave the LLM basic details about our league and how I want it to handle common user requests.

    Streamlit UI and Demo

    I’m not a frontend developer, so the UI leans heavily on Streamlit’s components and familiar chatbot patterns. Users input their Sleeper username, which is used to lookup their available leagues and persist memories across threads.

    I also added a couple of bells and whistles, like implementing token streaming so that users get instant feedback from the LLM. The other important piece is a “research pane”, which surfaces the results of the Agent’s tool calls so that user can inspect the raw data that informs each response.

    Here’s a quick demo.

    Deployment

    For development, I recommend deploying the components locally via the provided docker-compose.yml file. This will expose the API locally at http://localhost:8123 , so you can rapidly test changes and connect to it from a local Streamlit app.

    I have also included IaC for an AWS CDK-based deployment that I use to host the app on the internet. Most of the resources are defined here. Notice the parallels between the docker-compose.yml and the CDK code related to the ECS setup:

    Snippet from docker-compose.yml for the LangGraph API container:

    # from docker-compose.yml

    langgraph-api:
    image: "fantasy-chatbot"
    ports:
    - "8123:8000"
    healthcheck:
    test: curl --request GET --url http://localhost:8000/ok
    timeout: 1s
    retries: 5
    interval: 5s
    depends_on:
    langgraph-redis:
    condition: service_healthy
    langgraph-postgres:
    condition: service_healthy
    env_file: "../.env"
    environment:
    REDIS_URI: redis://langgraph-redis:6379
    POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable// file: fantasy-football-agent-stack.ts

    And here is the analogous setup in the CDK stack:

    // fantasy-football-agent-stack.ts

    const apiImageAsset = new DockerImageAsset(this, 'apiImageAsset', {
    directory: path.join(__dirname, '../../fantasy_chatbot'),
    file: 'api.Dockerfile',
    platform: assets.Platform.LINUX_AMD64,
    });
    const apiContainer = taskDefinition.addContainer('langgraph-api', {
    containerName: 'langgraph-api',
    image: ecs.ContainerImage.fromDockerImageAsset(apiImageAsset),
    portMappings: [{
    containerPort: 8000,
    }],
    environment: {
    ...dotenvMap,
    REDIS_URI: 'redis://127.0.0.1:6379',
    POSTGRES_URI: 'postgres://postgres:[email protected]:5432/postgres?sslmode=disable'
    },
    logging: ecs.LogDrivers.awsLogs({
    streamPrefix: 'langgraph-api',
    }),
    });

    apiContainer.addContainerDependencies(
    {
    container: redisContainer,
    condition: ecs.ContainerDependencyCondition.HEALTHY,
    },
    {
    container: postgresContainer,
    condition: ecs.ContainerDependencyCondition.HEALTHY,
    },
    )

    Aside from some subtle differences, it’s effectively a 1:1 translation, which is always something I look for when comparing local environments to “prod” deployments. The DockerImageAsset is a particularly useful resource, as it handles building and deploying (to ECR) the Docker image during synthesis.

    Note: Deploying the stack to your AWS account via npm run cdk deploy WILL incur charges. In this demo code I have not included any password protection on the Streamlit app, meaning anyone who has the URL can use the chatbot! I highly recommend adding some additional security if you plan to deploy it yourself.

    Takeaways

    You want to keep your tools simple. This app does a lot, but is still missing some key functionality, and it will start to break down if I simply add more tools. In the future, I want to break up the graph into task-specific sub-components, e.g. a “News Analyst” Agent and a “Statistician” Agent.

    Traceability and debugging are more important with Agent-based apps than traditional software. Despite significant advancements in models’ ability to produce structured outputs, LLM-based function calling is still inherently less reliable than conventional programs. I used LangSmith extensively for debugging.

    In an age of commoditized language models, there is no replacement for reliable reporters. We’re at a point where you can put together a reasonable chatbot in a weekend, so how do products differentiate themselves and build moats? This app (or any other like it) would be useless without access to high-quality reporting from analysts and experts. In other words, the Ian Rapaport’s and Matthew Berry’s of the world are more valuable than ever.

    Repo

    GitHub – evandiewald/fantasy-football-agent

    All images, unless otherwise noted, are by the author.


    Building a Fantasy Football Research Agent with LangGraph was originally published in Towards Data Science on Medium, where people are continuing the conversation by highlighting and responding to this story.

    Originally appeared here:
    Building a Fantasy Football Research Agent with LangGraph

    Go Here to Read this Fast! Building a Fantasy Football Research Agent with LangGraph

  • From Code to Paper: Using GPT Models and Python to Generate Scientific LaTeX Documents

    From Code to Paper: Using GPT Models and Python to Generate Scientific LaTeX Documents

    Peder Ward

    Automating scientific code documentation: a GPT-powered POC for streamlined workflows.

    Illustration picture. Generated by ChatGPT.

    Introduction

    Working on scientific papers often involves translating algorithms into scientific formulas, typically formatted in LaTeX. This process can be tedious and time-consuming, especially in large projects, as it requires constant back-and-forth between the code repository and the LaTeX document.

    While working on a large repository of algorithms, I began exploring ways to streamline this workflow. My motivation arose from the inefficiency of manually converting complex algorithms into LaTeX-compatible formulas. A particular challenge was ensuring consistency across multiple documents, especially in projects where formulas required frequent updates. This led me to explore how automation could streamline repetitive tasks while improving accuracy.

    For the remainder of this document, I will use both the term “algorithm” and “scientific code.” All images in this article, except for the cover image, were created by the author.

    Goal

    My goal was to transition from scientific code to a comprehensive document that introduces the purpose of the code, defines variables, presents scientific formulas, includes a generated example plot, and demonstrates the calculations for a specific example. The document would follow a predefined framework, combining static and dynamic elements to ensure both consistency and adaptability.

    The framework I designed included the following structure:

    1. Front Page
      A visually appealing cover with key details such as the title and author.
    2. Table of Contents
      Automatically generated to provide an overview of the document’s content.
    3. Brief Description of the Document
      An introduction outlining the purpose and scope of the document.
    4. Algorithms
      A section dedicated to documenting each algorithm in detail. For each algorithm, the following subsections would be included:
      Introduction: A brief overview of the algorithm’s purpose and context.
      Variables: A clear definition of all variables used in the algorithm.
      Formulas: A presentation of the key formulas derived from the algorithm.
      Example: A worked example to illustrate the algorithm’s application, complete with a generated plot.
      Code: The corresponding code snippet to support reproducibility.

    This structure was designed to dynamically adapt based on the number of algorithms being documented, ensuring a consistent and professional presentation regardless of the document’s size or complexity.

    Structuring the Repository

    To achieve this goal, a well-organized repository was essential for enabling a scalable and efficient solution. The algorithm calculations were grouped into a dedicated folder, with files named using a consistent snake_case convention that matched the algorithm names.

    To ensure clarity and support reuse, initial values for examples and the generated plots were stored in separate folders. These folders followed the same naming convention as the algorithms but with distinct suffixes to differentiate their purpose. This structure ensured that all components were easy to find and consistent with the overall framework of the project.

    Leveraging GPT for Automation

    At the core of this project is the use of GPT models to automate the conversion of algorithms into LaTeX. GPT’s strength lies in its ability to interpret the structure of generic, variable-rich code and transform it into human-readable explanations and precisely formatted scientific formulas. This automation significantly reduces the manual effort required, ensuring both accuracy and consistency across documents.

    For this project, I will leverage OpenAI’s ChatGPT-4o model, renowned for its advanced ability to comprehend and generate structured content. To interact with OpenAI’s API, you must have an OPENAI_KEY set in your environment. Below is a simple Python function I use to fetch responses from the GPT model:

    import os
    from openai import OpenAI
    from dotenv import load_dotenv

    def ask_chat_gpt(prompt):
    load_dotenv()
    api_key = os.getenv("OPENAI_KEY") or exit("API key missing")
    client = OpenAI(api_key=api_key)
    response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

    Workflow

    Overview of What the Code Does
    This code automates the generation of structured LaTeX documentation for Python algorithms, complete with examples, plots, and Python code listings. Here’s an overview:

    Flowchart of how the LaTeX Document is dynamically built. Made by author.

    Prompt Creation for GPT

    This section describes custom functions designed to generate detailed prompts for GPT, enabling the automated creation of LaTeX documentation:

    • make_algo_doc_gpt_prompt: A function that creates prompts instructing GPT to generate LaTeX sections, including introductions, variable descriptions, formulas, and example subsections.
    • make_algo_example_gpt_prompt: A function that generates prompts for creating LaTeX example sections, incorporating plots and example calculations.

    Document Generation

    These functions are responsible for processing the GPT-generated content and saving it as LaTeX files:

    • make_algo_doc: A function that uses GPT outputs to generate LaTeX documentation for each algorithm and saves it as a .tex file.
    • make_algo_example: A function that creates .tex files for example sections, including plots and example calculations.

    LaTeX Assembly

    • Uses the pylatex library to programmatically create a full LaTeX document.
    • Adds a title page, metadata, and a table of contents.
    • Includes an introduction section with an overview of the algorithms and their purpose.
    • Creates a chapter for each algorithm with sections from make_algo_doc and make_algo_example, example plots, and Python code listings.
    # Create and structure the LaTeX document programmatically
    doc = Document(documentclass="report")

    # Include preamble and metadata
    doc.preamble.append(NoEscape(r'input{algo_docs/init.tex}')) # Custom preamble
    doc.append(NoEscape(r'input{algo_docs/title_page.tex}')) # Title page
    doc.append(NoEscape(r'tableofcontents')) # Table of contents

    # Add Introduction Chapter
    with doc.create(Chapter('Introduction')):
    doc.append(
    'This document provides an overview of various algorithms, exploring their design, analysis, and application in computational problem-solving. '
    'The aim is to facilitate understanding of their mechanisms and significance across different domains.'
    )

    # Add Algorithms Chapter
    with doc.create(Chapter('Algorithms')):
    doc.append(
    'This chapter presents detailed analyses of various algorithms, highlighting their theoretical foundations, use cases, and practical insights. '
    'Each algorithm is accompanied by examples and visualizations to illustrate its functionality and potential limitations.'
    )

    # Process each Python file in the 'python_code' directory
    python_code_dir = "python_code/"
    output_folder = "algo_docs/"
    plot_folder = "plots/"

    for filename in os.listdir(python_code_dir):
    if filename.endswith(".py"): # Process only Python files
    algorithm_name = filename.replace(".py", "")
    formatted_name = algorithm_name.replace("_", " ").title()

    # Define paths for documentation files and plots
    document_path = os.path.join(output_folder, f"{algorithm_name}_doc.tex")
    example_path = os.path.join(output_folder, f"{algorithm_name}_example.tex")
    plot_path = os.path.join(plot_folder, f"{algorithm_name}_plot.png")
    python_code_path = os.path.join(python_code_dir, filename)

    print(f"Processing: {filename}")

    # Start a new page for each algorithm
    doc.append(NoEscape(r'newpage'))

    # Generate documentation and example files with GPT
    make_algo_doc(algorithm_name)
    make_algo_example(algorithm_name)

    # Insert generated LaTeX sections
    doc.append(NoEscape(rf'input{{{document_path}}}'))
    doc.append(NoEscape(rf'input{{{example_path}}}'))

    # Insert plot directly after example subsection
    if os.path.exists(plot_path):
    with doc.create(Figure(position='H')) as figure:
    figure.add_image(plot_path, width=NoEscape(r'textwidth'))
    figure.add_caption(f'Example plot for {formatted_name}.')

    # Add a subsection for the Python code listing
    with doc.create(Subsection('Code Listing')):
    doc.append(NoEscape(rf'lstinputlisting[language=Python]{{{python_code_path}}}'))

    # Add a page break for clarity
    doc.append(NoEscape(r'clearpage'))

    # Generate the LaTeX file
    tex_file = "programmatic_report"
    doc.generate_tex(tex_file)

    # Compile the LaTeX file to a PDF
    subprocess.run(["pdflatex", f"{tex_file}.tex"])

    PDF Compilation

    • The assembled document is saved and compiled into a polished PDF using pdflatex.
    Simple front page.
    Table of Contents.

    Crafting Effective Prompts: The Core Challenge

    One of the most challenging aspects of this project was designing and refining the prompts used to interact with GPT. The success of the entire process depended on the quality of the GPT-generated output, making the creation of effective prompts a critical task that required extensive time and experimentation.

    The prompts needed to strike a delicate balance:

    • Clarity: Precisely guiding GPT to produce structured LaTeX content, including sections, subsections, and mathematical equations, while leaving no ambiguity about the desired format.
    • Adaptability: Ensuring the prompts could handle a wide variety of algorithms, ranging from simple calculations to complex implementations.
    • Consistency: Achieving reliable, well-formatted, and accurate output, even for edge cases or unconventional code structures.

    To address these challenges, I implemented dynamic prompting. This approach involved programmatically generating prompts tailored to the contents of each file. By providing GPT with relevant context and specific instructions, dynamic prompting ensured the output was both accurate and contextually appropriate for the given algorithm.

    Through numerous iterations, the prompts evolved to become precise and flexible, forming the foundation of the automation process.
    Example of a prompt for generating LaTeX code from a algorithm:

    Generate LaTeX code from the provided Python code. Follow these guidelines:

    1. **Document Structure**:
    - Start with `\section{}` for the algorithm title.
    - Add a `\subsection{Introduction}` for a brief overview of the algorithm.
    - Include a `\subsection{Variables}` section that lists all variables with descriptions, using subscript notation (e.g., `v_{\text{earth}}`).
    - Add a `\subsection{Formulas}` section presenting the code's logic as LaTeX formulas. Use subscripted symbols for variable names instead of copying Python variable names directly.

    2. **Formatting Rules**:
    - Ensure that the output includes **only** the LaTeX content, without `\documentclass`, `\usepackage`, `\begin{document}`, `\end{document}`, or any unrelated text.
    - Do **not** include the triple backticks (e.g., ```latex or ```).
    - Properly close all LaTeX environments (e.g., `\begin{align*}...\end{align*}`).
    - Ensure all brackets, parentheses, and braces are matched correctly.
    - Maintain consistent subscript notation for all variables.

    3. **Important Notes**:
    - **Do not** include any text or explanations outside the LaTeX code.
    - Only the relevant LaTeX content for the `\section`, `\subsection`, `\begin{align*}`, and `\end{align*}` parts should be generated.
    - Ensure no extra or unrelated LaTeX sections are added.

    Example: Hohmann Transfer Orbit Calculation

    The following demonstrates how the Hohmann Transfer Orbit Calculation algorithm is documented using GPT-generated LaTeX code. This algorithm calculates the velocity changes (delta-v) required to transfer a spacecraft from Earth’s orbit to Mars’s orbit. Below is the Python implementation of the algorithm:

    def calculate_hohmann_transfer(earth_orbit_radius, mars_orbit_radius):
    # Gravitational constant for the Sun
    mu_sun = 1.32712440018e20

    # Orbital velocities of Earth and Mars
    v_earth = np.sqrt(mu_sun / earth_orbit_radius)
    v_mars = np.sqrt(mu_sun / mars_orbit_radius)

    # Semi-major axis of the transfer orbit
    transfer_orbit_semi_major_axis = (earth_orbit_radius + mars_orbit_radius) / 2

    # Transfer orbit velocities at Earth and Mars
    v_transfer_at_earth = np.sqrt(2 * mu_sun / earth_orbit_radius - mu_sun / transfer_orbit_semi_major_axis)
    v_transfer_at_mars = np.sqrt(2 * mu_sun / mars_orbit_radius - mu_sun / transfer_orbit_semi_major_axis)

    # Delta-v at Earth and Mars
    delta_v_earth = v_transfer_at_earth - v_earth
    delta_v_mars = v_mars - v_transfer_at_mars

    # Total delta-v for the transfer
    total_delta_v = abs(delta_v_earth) + abs(delta_v_mars)

    return delta_v_earth, delta_v_mars, total_delta_v

    Using the GPT prompt with this code, I generated LaTeX subsections for the documentation. Below are the components created:

    Introduction to the Algorithm
    GPT generated a LaTeX explanation of the algorithm’s purpose, detailing how it calculates velocity changes for an efficient interplanetary transfer.

    Introduction to the algorithm. LaTeX code generated by GPT model.

    Variable Definitions
    GPT provided a clear explanation of all variables used in the algorithm.

    Variable definitions for the algorithm. LaTeX code generated by GPT model.

    Formulas
    The key formulas used in the algorithm were formatted into LaTeX by GPT.

    Formulas used in the algorithm. LaTeX code generated by GPT model.

    Example Section
    Using example values, GPT generated LaTeX code for a worked example.

    Snip of the Example using example values and the algorithm as input. LaTeX code generated by GPT model.

    Plot Generation
    A plot of the transfer orbit was generated using the example values and included in the LaTeX document.

    Plot generated by the code and example values. Inserted in the LaTeX dokument.

    Code Listing
    The algorithm’s source code was appended to the document for completeness at the end.

    Code listings at the end of the chapter (partial view).

    Results and Challenges

    Initial experiments with this system have been promising. Using Python and GPT-4, I successfully automated the conversion of several algorithms into LaTeX documents. The results of this proof of concept (POC) can be explored in my GitHub repository, where all aspects of the project are available for review.

    The repository includes the complete Python codebase, showcasing the custom functions used to generate LaTeX documentation and create GPT prompts. It also contains the detailed prompts themselves, illustrating how the system guides GPT in producing structured and accurate LaTeX content. Additionally, the repository features the final outputs, including both the LaTeX source files and the compiled PDF documents.

    While the initial results have been promising, the process has not been without its challenges and valuable insights along the way::

    • Formatting Challenges: Occasionally, GPT would produce incorrect LaTeX formatting, leading to errors during the PDF conversion process. Although this issue was rare, I experimented with a solution: resubmitting the LaTeX code to GPT and asking it to fix the formatting. While this approach was consistently successful, it was not implemented as part of the workflow.
    • Code Comments: Adding clear comments within the code helped GPT understand the context better and generate more accurate LaTeX outputs.
    • Inconsistent Results: GPT occasionally produced varying outputs for the same code and prompt, emphasizing its inherent variability and the importance of careful testing.
    • Crafting Effective Prompts: Writing effective prompts was challenging. Overloading the prompt with too much detail, like examples, often caused GPT to miss smaller elements such as formatting or structure. I discovered that breaking down instructions step by step and using very small, focused examples helped GPT perform better. Keeping prompts concise and structured with bullet points ensured that each key instruction was clearly understood and executed.
    • Domain-Specific Terminology: Fine-tuning GPT for specialized terms is an area requiring further improvement to enhance accuracy.
    • Variable Definitions: Keeping LaTeX variable definations in algorithm and examples consistent was challenging. Adding GPT-generated variable definitions to later prompts helped maintain uniformity.

    Despite its imperfections, the workflow has drastically reduced the time spent on documentation by automating much of the process. While minor reviews and adjustments are still needed, they represent only a fraction of the effort previously required. This proof of concept demonstrates the potential to generate polished documents without writing LaTeX manually, though further refinement is needed to enhance consistency, scalability, and adaptability. The results so far highlight the significant promise of this approach.

    Improvements

    • Develop Validation Mechanisms
      Implement cross-referencing of generated formulas against known standards or benchmarks to ensure accuracy and consistency.
    • Expand Use Cases
      Test the workflow on larger, more diverse datasets to improve scalability and adaptability for various scientific domains.
    • Enhance Visual Documentation
      Incorporate additional visual elements, such as flowcharts, by using GPT to generate XML documents or similar formats.
    • Generate Plots and Examples with GPT
      Extend GPT’s functionality to create example plots directly, reducing the reliance on external plotting tools.
    • Experiment with Different GPT Models
      Thus far, I have primarily used ChatGPT-4 due to its accessibility, but further research is needed to identify the optimal model for this task. Exploring models tailored for technical content or incorporating a Retrieval-Augmented Generation (RAG) approach with a database of diverse scientific papers could improve accuracy and relevance.
    • Transition from Proof of Concept (POC) to Minimum Viable Product (MVP)
      Evolve the project from a proof of concept to a minimum viable product by adding robust error handling, scalability features, and user-focused refinements.

    Conclusion

    This project has proven the potential of GPT models to automate the creation of structured LaTeX documentation, significantly reducing the manual effort involved. It successfully generated professional-quality outputs, including formulas, plots, and structured examples. However, challenges such as inconsistent results, formatting issues, and variability in GPT’s output highlighted the need for refinement. Strategies like dynamic prompting, better code commenting, and iterative validation have helped address these issues, but some manual oversight remains necessary.

    Despite these challenges, the workflow has shown clear benefits, streamlining the documentation process and saving considerable time. While the solution is not yet perfect, it represents a significant step toward automating complex documentation tasks, paving the way for future improvements in accuracy.

    LinkedIn Profile — Peder Ward

    GitHub — Peder Ward


    From Code to Paper: Using GPT Models and Python to Generate Scientific LaTeX Documents was originally published in Towards Data Science on Medium, where people are continuing the conversation by highlighting and responding to this story.

    Originally appeared here:
    From Code to Paper: Using GPT Models and Python to Generate Scientific LaTeX Documents

    Go Here to Read this Fast! From Code to Paper: Using GPT Models and Python to Generate Scientific LaTeX Documents